From: Numan Siddique <[email protected]>

This commit is the first of the series to add a new service
to OVN called ovn-br-controller (OVN bridge controller).
This commit adds
 - a schema file
 - a basic skeleton of ovn-br-controller service (which does nothing)
 - ovn-brctl utility similar to ovn-nbctl/ovn-sbctl.

Rational for adding this service:
There's a need to configure the provider bridges with specific OpenFlow
rules after packets leave the OVN pipeline and enters these provider
bridges  via the patch ports.  Since OVN has a very good abstraction
of the OpenFlow rules using OVN logical flows, we can leverage the same
here so that CMS doesn't have to deal with the specifics of OpenFlow
protocol.  Also if the CMS is written in golang or other languages,
CMS has to mostly rely on ovs-vsctl/ovs-ofctl to program the flows.
Adding this support in OVN avoids the CMS to exec these utils to
add/delete and dump the existing OpenFlow rules.

Any user can also use this new service to program and manage any OVS
bridge without using OVN and hence this service is named as
"ovn-br-controller."

This commit also adds ovn-br-controller sub package for fedora/rhel.

Signed-off-by: Numan Siddique <[email protected]>
---
 Makefile.am                                   |   5 +-
 automake.mk                                   |  36 ++
 br-controller/automake.mk                     |   8 +
 br-controller/ovn-br-controller.8.xml         |  24 +
 br-controller/ovn-br-controller.c             | 175 ++++++
 lib/automake.mk                               |  17 +-
 lib/ovn-br-idl.ann                            |   9 +
 lib/ovn-util.c                                |  31 ++
 lib/ovn-util.h                                |  16 +
 ovn-br.ovsschema                              |  94 ++++
 ovn-br.xml                                    | 452 +++++++++++++++
 rhel/automake.mk                              |   4 +-
 rhel/ovn-fedora.spec.in                       |  22 +-
 ...b_systemd_system_ovn-br-controller.service |  35 ++
 rhel/usr_lib_systemd_system_ovn-br-db.service |  32 ++
 tutorial/ovn-sandbox                          |  24 +-
 utilities/automake.mk                         |   8 +
 utilities/ovn-brctl.c                         | 524 ++++++++++++++++++
 utilities/ovn-sbctl.c                         |  30 -
 19 files changed, 1511 insertions(+), 35 deletions(-)
 create mode 100644 br-controller/automake.mk
 create mode 100644 br-controller/ovn-br-controller.8.xml
 create mode 100644 br-controller/ovn-br-controller.c
 create mode 100644 lib/ovn-br-idl.ann
 create mode 100644 ovn-br.ovsschema
 create mode 100644 ovn-br.xml
 create mode 100644 rhel/usr_lib_systemd_system_ovn-br-controller.service
 create mode 100644 rhel/usr_lib_systemd_system_ovn-br-db.service
 create mode 100644 utilities/ovn-brctl.c

diff --git a/Makefile.am b/Makefile.am
index 4ea4ae6c52..3ad2077b37 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -115,7 +115,9 @@ EXTRA_DIST = \
        ovn-ic-nb.ovsschema \
        ovn-ic-nb.xml \
        ovn-ic-sb.ovsschema \
-       ovn-ic-sb.xml
+       ovn-ic-sb.xml \
+       ovn-br.ovsschema \
+       ovn-br.xml
 bin_PROGRAMS =
 sbin_PROGRAMS =
 bin_SCRIPTS =
@@ -512,4 +514,5 @@ include controller/automake.mk
 include controller-vtep/automake.mk
 include northd/automake.mk
 include ic/automake.mk
+include br-controller/automake.mk
 include build-aux/automake.mk
diff --git a/automake.mk b/automake.mk
index a7947a3f54..53860ad89b 100644
--- a/automake.mk
+++ b/automake.mk
@@ -120,6 +120,35 @@ ovn-ic-sb.5: \
                $(srcdir)/ovn-ic-sb.xml > [email protected] && \
        mv [email protected] $@
 
+# OVN bridge controller E-R diagram
+#
+# If "python" or "dot" is not available, then we do not add graphical diagram
+# to the documentation.
+if HAVE_DOT
+ovn-br.gv: ${OVSDIR}/ovsdb/ovsdb-dot.in $(srcdir)/ovn-br.ovsschema
+       $(AM_V_GEN)$(OVSDB_DOT) --no-arrows $(srcdir)/ovn-br.ovsschema > $@
+ovn-br.pic: ovn-br.gv ${OVSDIR}/ovsdb/dot2pic
+       $(AM_V_GEN)(dot -T plain < ovn-br.gv | $(PYTHON3) 
${OVSDIR}/ovsdb/dot2pic -f 3) > [email protected] && \
+       mv [email protected] $@
+OVN_BR_PIC = ovn-br.pic
+OVN_BR_DOT_DIAGRAM_ARG = --er-diagram=$(OVN_BR_PIC)
+CLEANFILES += ovn-br.gv ovn-br.pic
+endif
+
+# OVN bridge controller schema documentation
+EXTRA_DIST += ovn-br.xml
+CLEANFILES += ovn-br.5
+man_MANS += ovn-br.5
+
+ovn-br.5: \
+       ${OVSDIR}/ovsdb/ovsdb-doc $(srcdir)/ovn-br.xml 
$(srcdir)/ovn-br.ovsschema $(OVN_BR_PIC)
+       $(AM_V_GEN)$(OVSDB_DOC) \
+               $(OVN_BR_DOT_DIAGRAM_ARG) \
+               --version=$(VERSION) \
+               $(srcdir)/ovn-br.ovsschema \
+               $(srcdir)/ovn-br.xml > [email protected] && \
+       mv [email protected] $@
+
 # Version checking for ovn-nb.ovsschema.
 ALL_LOCAL += ovn-nb.ovsschema.stamp
 ovn-nb.ovsschema.stamp: ovn-nb.ovsschema
@@ -143,9 +172,16 @@ ovn-ic-sb.ovsschema.stamp: ovn-ic-sb.ovsschema
        $(srcdir)/build-aux/cksum-schema-check $? $@
 CLEANFILES += ovn-ic-sb.ovsschema.stamp
 
+# Version checking for ovn-br.ovsschema.
+ALL_LOCAL += ovn-br.ovsschema.stamp
+ovn-br.ovsschema.stamp: ovn-br.ovsschema
+       $(srcdir)/build-aux/cksum-schema-check $? $@
+CLEANFILES += ovn-br.ovsschema.stamp
+
 pkgdata_DATA += ovn-nb.ovsschema
 pkgdata_DATA += ovn-sb.ovsschema
 pkgdata_DATA += ovn-ic-nb.ovsschema
 pkgdata_DATA += ovn-ic-sb.ovsschema
+pkgdata_DATA += ovn-br.ovsschema
 
 CLEANFILES += ovn-sb.ovsschema.stamp
diff --git a/br-controller/automake.mk b/br-controller/automake.mk
new file mode 100644
index 0000000000..012e9a5ed8
--- /dev/null
+++ b/br-controller/automake.mk
@@ -0,0 +1,8 @@
+bin_PROGRAMS += br-controller/ovn-br-controller
+br_controller_ovn_br_controller_SOURCES = \
+       br-controller/ovn-br-controller.c
+
+br_controller_ovn_br_controller_LDADD = lib/libovn.la 
$(OVS_LIBDIR)/libopenvswitch.la
+man_MANS += br-controller/ovn-br-controller.8
+EXTRA_DIST += br-controller/ovn-br-controller.8.xml
+CLEANFILES += br-controller/ovn-br-controller.8
diff --git a/br-controller/ovn-br-controller.8.xml 
b/br-controller/ovn-br-controller.8.xml
new file mode 100644
index 0000000000..be81bd9188
--- /dev/null
+++ b/br-controller/ovn-br-controller.8.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manpage program="ovn-br-controller" section="8" title="ovn-br-controller">
+    <h1>Name</h1>
+    <p>ovn-br-controller -- Open Virtual Network local OVS bridge 
controller</p>
+
+    <h1>Synopsis</h1>
+    <p>
+      <code>ovn-br-controller</code> [<var>options</var>]
+      [<var>ovs-database</var>]
+    </p>
+
+    <h1>Description</h1>
+    <p>
+      <code>ovn-br-controller</code> is OVN logical flow based
+      local controller daemon to control and program the Open vSwitch
+      bridges.  It connects up to the OVN bridge database (see 
<code>ovn-br</code>(5))
+      over the OVSDB protocol, and down to the Open vSwitch database (see
+      <code>ovs-vswitchd.conf.db</code>(5)) over the OVSDB protocol and
+      to <code>ovs-vswitchd</code>(8) via OpenFlow.  
<code>ovn-br-controller</code>
+      processes and converts the OVN Logical flows configured in the OVN 
bridge database
+      for the Open vSwitch bridges to OpenFlow rules and programs these flow 
rules to the
+      Open vSwitch bridges.
+    </p>
+</manpage>
diff --git a/br-controller/ovn-br-controller.c 
b/br-controller/ovn-br-controller.c
new file mode 100644
index 0000000000..0fef0e5fee
--- /dev/null
+++ b/br-controller/ovn-br-controller.c
@@ -0,0 +1,175 @@
+/* Copyright (c) 2025 Crusoe Energy Systems LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* OVS includes. */
+#include "openvswitch/vlog.h"
+#include "lib/command-line.h"
+#include "lib/daemon.h"
+#include "lib/dirs.h"
+#include "lib/fatal-signal.h"
+#include "lib/stream.h"
+#include "lib/stream-ssl.h"
+#include "lib/unixctl.h"
+
+/* OVN includes. */
+#include "lib/ovn-br-idl.h"
+#include "lib/ovn-util.h"
+
+VLOG_DEFINE_THIS_MODULE(main);
+
+static void parse_options(int argc, char *argv[]);
+OVS_NO_RETURN static void usage(void);
+
+
+/* SSL/TLS options. */
+static const char *ssl_private_key_file;
+static const char *ssl_certificate_file;
+static const char *ssl_ca_cert_file;
+
+/* --unixctl-path: Path to use for unixctl server socket. */
+static char *unixctl_path;
+
+int
+main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    ovs_cmdl_proctitle_init(argc, argv);
+    ovn_set_program_name(argv[0]);
+    service_start(&argc, &argv);
+    parse_options(argc, argv);
+    fatal_ignore_sigpipe();
+
+    return 0;
+}
+
+/* static functions. */
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_PEER_CA_CERT = UCHAR_MAX + 1,
+        OPT_BOOTSTRAP_CA_CERT,
+        VLOG_OPTION_ENUMS,
+        OVN_DAEMON_OPTION_ENUMS,
+        SSL_OPTION_ENUMS,
+    };
+
+    static struct option long_options[] = {
+        {"help", no_argument, NULL, 'h'},
+        {"version", no_argument, NULL, 'V'},
+        {"unixctl", required_argument, NULL, 'u'},
+        VLOG_LONG_OPTIONS,
+        OVN_DAEMON_LONG_OPTIONS,
+        STREAM_SSL_LONG_OPTIONS,
+        {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
+        {"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
+        {NULL, 0, NULL, 0}
+    };
+    char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'V':
+            ovs_print_version(OFP15_VERSION, OFP15_VERSION);
+            printf("OVN BR DB Schema %s\n", ovnbrrec_get_db_version());
+            exit(EXIT_SUCCESS);
+
+        case 'u':
+            unixctl_path = optarg;
+            break;
+
+        VLOG_OPTION_HANDLERS
+        OVN_DAEMON_OPTION_HANDLERS
+
+        case 'p':
+            ssl_private_key_file = optarg;
+            break;
+
+        case 'c':
+            ssl_certificate_file = optarg;
+            break;
+
+        case 'C':
+            ssl_ca_cert_file = optarg;
+            break;
+
+        case OPT_SSL_PROTOCOLS:
+            stream_ssl_set_protocols(optarg);
+            break;
+
+        case OPT_SSL_CIPHERS:
+            stream_ssl_set_ciphers(optarg);
+            break;
+
+        case OPT_SSL_CIPHERSUITES:
+            stream_ssl_set_ciphersuites(optarg);
+            break;
+
+        case OPT_PEER_CA_CERT:
+            stream_ssl_set_peer_ca_cert_file(optarg);
+            break;
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            ovs_abort(0, "Invalid option.");
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+}
+
+static void
+usage(void)
+{
+    printf("%s: OVN bridge controller\n"
+           "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, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -u, --unixctl=SOCKET    set control socket name\n"
+           "  -n                      custom chassis name\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n");
+    exit(EXIT_SUCCESS);
+}
diff --git a/lib/automake.mk b/lib/automake.mk
index a59c722d62..46c24e99d1 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -59,7 +59,9 @@ nodist_lib_libovn_la_SOURCES = \
        lib/ovn-ic-nb-idl.c \
        lib/ovn-ic-nb-idl.h \
        lib/ovn-ic-sb-idl.c \
-       lib/ovn-ic-sb-idl.h
+       lib/ovn-ic-sb-idl.h \
+       lib/ovn-br-idl.c \
+       lib/ovn-br-idl.h
 
 CLEANFILES += $(nodist_lib_libovn_la_SOURCES)
 
@@ -129,3 +131,16 @@ OVN_IC_SB_IDL_FILES = \
 lib/ovn-ic-sb-idl.ovsidl: $(OVN_IC_SB_IDL_FILES)
        $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_IC_SB_IDL_FILES) > [email protected] && \
        mv [email protected] $@
+
+# ovn-br IDL
+OVSIDL_BUILT += \
+       lib/ovn-br-idl.c \
+       lib/ovn-br-idl.h \
+       lib/ovn-br-idl.ovsidl
+EXTRA_DIST += lib/ovn-br-idl.ann
+OVN_PR_IDL_FILES = \
+       $(srcdir)/ovn-br.ovsschema \
+       $(srcdir)/lib/ovn-br-idl.ann
+lib/ovn-br-idl.ovsidl: $(OVN_PR_IDL_FILES)
+       $(AM_V_GEN)$(OVSDB_IDLC) annotate $(OVN_PR_IDL_FILES) > [email protected] && \
+       mv [email protected] $@
diff --git a/lib/ovn-br-idl.ann b/lib/ovn-br-idl.ann
new file mode 100644
index 0000000000..80993338a6
--- /dev/null
+++ b/lib/ovn-br-idl.ann
@@ -0,0 +1,9 @@
+# -*- python -*-
+
+# This code, when invoked by "ovsdb-idlc annotate" (by the build
+# process), annotates ovn-br.ovsschema with additional data that give
+# the ovsdb-idl engine information about the types involved, so that
+# it can generate more programmer-friendly data structures.
+
+s["idlPrefix"] = "ovnbrrec_"
+s["idlHeader"] = "\"lib/ovn-br-idl.h\""
diff --git a/lib/ovn-util.c b/lib/ovn-util.c
index d27983d1eb..2afa0972c7 100644
--- a/lib/ovn-util.c
+++ b/lib/ovn-util.c
@@ -566,6 +566,19 @@ default_ic_sb_db(void)
     return def;
 }
 
+const char *
+default_br_db(void)
+{
+    static char *def;
+    if (!def) {
+        def = getenv("OVN_BR_DB");
+        if (!def) {
+            def = xasprintf("unix:%s/ovnbr_db.sock", ovn_rundir());
+        }
+    }
+    return def;
+}
+
 char *
 get_abs_unix_ctl_path(const char *path)
 {
@@ -1648,3 +1661,21 @@ normalize_addr_str(const char *orig_addr)
 
     return ret;
 }
+
+bool
+is_partial_uuid_match(const struct uuid *uuid, const char *match)
+{
+    char uuid_s[UUID_LEN + 1];
+    snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid));
+
+    /* We strip leading zeros because we want to accept cookie values derived
+     * from UUIDs, and cookie values are printed without leading zeros because
+     * they're just numbers. */
+    const char *s1 = strip_leading_zero(uuid_s);
+    const char *s2 = match;
+    if (is_uuid_with_prefix(s2)) {
+        s2 = s2 + 2;
+    }
+    s2 = strip_leading_zero(s2);
+    return !strncmp(s1, s2, strlen(s2));
+}
diff --git a/lib/ovn-util.h b/lib/ovn-util.h
index 7a5bb9559e..9fc171b71c 100644
--- a/lib/ovn-util.h
+++ b/lib/ovn-util.h
@@ -134,6 +134,7 @@ const char *default_nb_db(void);
 const char *default_sb_db(void);
 const char *default_ic_nb_db(void);
 const char *default_ic_sb_db(void);
+const char *default_br_db(void);
 char *get_abs_unix_ctl_path(const char *path);
 
 struct ovsdb_idl_table_class;
@@ -594,6 +595,21 @@ dynamic_bitmap_scan(struct dynamic_bitmap *dp, bool 
target, size_t start)
 #define DYNAMIC_BITMAP_FOR_EACH_1(IDX, MAP)   \
         BITMAP_FOR_EACH_1(IDX, (MAP)->capacity, (MAP)->map)
 
+
+static inline const char *
+strip_leading_zero(const char *s)
+{
+    return s + strspn(s, "0");
+}
+
+static inline bool
+is_uuid_with_prefix(const char *uuid)
+{
+     return uuid[0] == '0' && (uuid[1] == 'x' || uuid[1] == 'X');
+}
+
+bool is_partial_uuid_match(const struct uuid *uuid, const char *match);
+
 /* Utilities around properly handling exit command. */
 struct ovn_exit_args {
     struct unixctl_conn **conns;
diff --git a/ovn-br.ovsschema b/ovn-br.ovsschema
new file mode 100644
index 0000000000..1fc66ec99e
--- /dev/null
+++ b/ovn-br.ovsschema
@@ -0,0 +1,94 @@
+{
+    "name": "OVN_Bridge_Controller",
+    "version": "0.0.1",
+    "cksum": "1701378742 4390",
+    "tables": {
+        "BR_Global": {
+            "columns": {
+                "br_cfg": {"type": {"key": "integer"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}},
+                "connections": {
+                        "type": {"key": {"type": "uuid",
+                                        "refTable": "Connection"},
+                                        "min": 0,
+                                        "max": "unlimited"}},
+                "ssl": {
+                    "type": {"key": {"type": "uuid",
+                                    "refTable": "SSL"},
+                                    "min": 0, "max": 1}},
+                "options": {
+                    "type": {"key": "string", "value": "string",
+                            "min": 0, "max": "unlimited"}}},
+            "maxRows": 1,
+            "isRoot": true},
+        "Connection": {
+            "columns": {
+                "target": {"type": "string"},
+                "max_backoff": {"type": {"key": {"type": "integer",
+                                         "minInteger": 1000},
+                                         "min": 0,
+                                         "max": 1}},
+                "inactivity_probe": {"type": {"key": "integer",
+                                              "min": 0,
+                                              "max": 1}},
+                "other_config": {"type": {"key": "string",
+                                          "value": "string",
+                                          "min": 0,
+                                          "max": "unlimited"}},
+                "external_ids": {"type": {"key": "string",
+                                 "value": "string",
+                                 "min": 0,
+                                 "max": "unlimited"}},
+                "is_connected": {"type": "boolean", "ephemeral": true},
+                "status": {"type": {"key": "string",
+                                    "value": "string",
+                                    "min": 0,
+                                    "max": "unlimited"},
+                                    "ephemeral": true}},
+            "indexes": [["target"]]},
+        "SSL": {
+            "columns": {
+                "private_key": {"type": "string"},
+                "certificate": {"type": "string"},
+                "ca_cert": {"type": "string"},
+                "bootstrap_ca_cert": {"type": "boolean"},
+                "ssl_protocols": {"type": "string"},
+                "ssl_ciphers": {"type": "string"},
+                "ssl_ciphersuites": {"type": "string"},
+                "external_ids": {"type": {"key": "string",
+                                          "value": "string",
+                                          "min": 0,
+                                          "max": "unlimited"}}},
+            "maxRows": 1},
+        "Bridge": {
+            "columns": {
+                "name": {"type": "string"},
+                "options": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "indexes": [["name"]],
+            "isRoot": true},
+        "Logical_Flow": {
+            "columns": {
+                "bridge": {"type": {"key": {"type": "uuid",
+                                      "refTable": "Bridge"},
+                           "min": 0, "max": 1}},
+                "table_id": {"type": {"key": {"type": "integer",
+                                              "minInteger": 0,
+                                              "maxInteger": 100}}},
+                "priority": {"type": {"key": {"type": "integer",
+                                              "minInteger": 0,
+                                              "maxInteger": 65535}}},
+                "match": {"type": "string"},
+                "actions": {"type": "string"},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "isRoot": true}
+    }
+}
diff --git a/ovn-br.xml b/ovn-br.xml
new file mode 100644
index 0000000000..be6232094b
--- /dev/null
+++ b/ovn-br.xml
@@ -0,0 +1,452 @@
+<?xml version="1.0" encoding="utf-8"?>
+<database name="ovn-bridge-controller" title="OVN Bridge Controller Database">
+  <p>
+    This database is the interface between OVN Bridge Controller and the
+    cloud management system (CMS) to program and control the OVS bridges
+    using OVN logical flows. The CMS produces almost all of
+    the contents of the database.  The <code>ovn-bridge-controller</code> 
program
+    monitors the database contents and programs the OVS bridges with the
+    OpenFlow rules.
+  </p>
+
+  <h2>External IDs</h2>
+
+  <p>
+    Each of the tables in this database contains a special column, named
+    <code>external_ids</code>.  This column has the same form and purpose each
+    place it appears.
+  </p>
+
+  <dl>
+    <dt><code>external_ids</code>: map of string-string pairs</dt>
+    <dd>
+      Key-value pairs for use by the CMS.  The CMS might use certain pairs, for
+      example, to identify entities in its own configuration that correspond to
+      those in this database.
+    </dd>
+  </dl>
+
+  <table name="BR_Global" title="Bridge Controller configuration">
+    <column name="br_cfg">
+      Sequence number for client to increment.  When a client modifies any
+      part of the bridge Controller database configuration and wishes to wait
+      for <code>ovn-br-controller</code> to finish applying the changes, it may
+      increment this sequence number.
+    </column>
+
+    <group title="Common Columns">
+        <column name="external_ids">
+        See <em>External IDs</em> at the beginning of this document.
+      </column>
+    </group>
+
+    <group title="Common options">
+      <column name="options">
+        This column provides general key/value settings. The supported
+        options are described individually below.
+      </column>
+    </group>
+
+    <group title="Connection Options">
+      <column name="connections">
+        Database clients to which the Open vSwitch database server should
+        connect or on which it should listen, along with options for how these
+        connections should be configured.  See the <ref table="Connection"/>
+        table for more information.
+      </column>
+      <column name="ssl">
+        Global SSL/TLS configuration.
+      </column>
+    </group>
+  </table>
+
+  <table name="Connection" title="OVSDB client connections.">
+    <p>
+      Configuration for a database connection to an Open vSwitch database
+      (OVSDB) client.
+    </p>
+
+    <p>
+      This table primarily configures the Open vSwitch database server
+      (<code>ovsdb-server</code>).
+    </p>
+
+    <p>
+      The Open vSwitch database server can initiate and maintain active
+      connections to remote clients.  It can also listen for database
+      connections.
+    </p>
+
+    <group title="Core Features">
+      <column name="target">
+        <p>Connection methods for clients.</p>
+        <p>
+          The following connection methods are currently supported:
+        </p>
+        <dl>
+          
<dt><code>ssl:<var>host</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>
+            <p>
+              The specified SSL/TLS <var>port</var> on the host at the given
+              <var>host</var>, which can either be a DNS name (if built with
+              unbound library) or an IP address. A valid SSL/TLS configuration
+              must be provided when this form is used, this configuration can
+              be specified via command-line options or the <ref table="SSL"/>
+              table.
+            </p>
+            <p>
+              If <var>port</var> is not specified, it defaults to 6640.
+            </p>
+            <p>
+              SSL/TLS support is an optional feature that is not always
+              built as part of OVN or Open vSwitch.
+            </p>
+          </dd>
+
+          
<dt><code>tcp:<var>host</var></code>[<code>:<var>port</var></code>]</dt>
+          <dd>
+            <p>
+              The specified TCP <var>port</var> on the host at the given
+              <var>host</var>, which can either be a DNS name (if built with
+              unbound library) or an IP address.  If <var>host</var> is an IPv6
+              address, wrap it in square brackets, e.g. 
<code>tcp:[::1]:6640</code>.
+            </p>
+            <p>
+              If <var>port</var> is not specified, it defaults to 6640.
+            </p>
+          </dd>
+          
<dt><code>pssl:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt>
+          <dd>
+            <p>
+              Listens for SSL/TLS connections on the specified TCP
+              <var>port</var>.
+              Specify 0 for <var>port</var> to have the kernel automatically
+              choose an available port.  If <var>host</var>, which can either
+              be a DNS name (if built with unbound library) or an IP address,
+              is specified, then connections are restricted to the resolved or
+              specified local IPaddress (either IPv4 or IPv6 address).  If
+              <var>host</var> is an IPv6 address, wrap in square brackets,
+              e.g. <code>pssl:6640:[::1]</code>.  If <var>host</var> is not
+              specified then it listens only on IPv4 (but not IPv6) addresses.
+              A valid SSL/TLS configuration must be provided when this form is
+              used, this can be specified either via command-line options or
+              the <ref table="SSL"/> table.
+            </p>
+            <p>
+              If <var>port</var> is not specified, it defaults to 6640.
+            </p>
+            <p>
+              SSL/TLS support is an optional feature that is not always built
+              as part of OVN or Open vSwitch.
+            </p>
+          </dd>
+          
<dt><code>ptcp:</code>[<var>port</var>][<code>:<var>host</var></code>]</dt>
+          <dd>
+            <p>
+              Listens for connections on the specified TCP <var>port</var>.
+              Specify 0 for <var>port</var> to have the kernel automatically
+              choose an available port.  If <var>host</var>, which can either
+              be a DNS name (if built with unbound library) or an IP address,
+              is specified, then connections are restricted to the resolved or
+              specified local IP address (either IPv4 or IPv6 address).  If
+              <var>host</var> is an IPv6 address, wrap it in square brackets,
+              e.g. <code>ptcp:6640:[::1]</code>.  If <var>host</var> is not
+              specified then it listens only on IPv4 addresses.
+            </p>
+            <p>
+              If <var>port</var> is not specified, it defaults to 6640.
+            </p>
+          </dd>
+        </dl>
+        <p>When multiple clients are configured, the <ref column="target"/>
+        values must be unique.  Duplicate <ref column="target"/> values yield
+        unspecified results.</p>
+      </column>
+    </group>
+
+    <group title="Client Failure Detection and Handling">
+      <column name="max_backoff">
+        Maximum number of milliseconds to wait between connection attempts.
+        Default is implementation-specific.
+      </column>
+
+      <column name="inactivity_probe">
+        Maximum number of milliseconds of idle time on connection to the client
+        before sending an inactivity probe message.  If Open vSwitch does not
+        communicate with the client for the specified number of seconds, it
+        will send a probe.  If a response is not received for the same
+        additional amount of time, Open vSwitch assumes the connection has been
+        broken and attempts to reconnect.  Default is implementation-specific.
+        A value of 0 disables inactivity probes.
+      </column>
+    </group>
+
+    <group title="Status">
+      <p>
+        Key-value pair of <ref column="is_connected"/> is always updated.
+        Other key-value pairs in the status columns may be updated depends
+        on the <ref column="target"/> type.
+      </p>
+
+      <p>
+        When <ref column="target"/> specifies a connection method that
+        listens for inbound connections (e.g. <code>ptcp:</code> or
+        <code>punix:</code>), both <ref column="n_connections"/> and
+        <ref column="is_connected"/> may also be updated while the
+        remaining key-value pairs are omitted.
+      </p>
+
+      <p>
+        On the other hand, when <ref column="target"/> specifies an
+        outbound connection, all key-value pairs may be updated, except
+        the above-mentioned two key-value pairs associated with inbound
+        connection targets. They are omitted.
+      </p>
+
+      <column name="is_connected">
+        <code>true</code> if currently connected to this client,
+        <code>false</code> otherwise.
+      </column>
+
+      <column name="status" key="last_error">
+        A human-readable description of the last error on the connection
+        to the manager; i.e. <code>strerror(errno)</code>.  This key
+        will exist only if an error has occurred.
+      </column>
+
+      <column name="status" key="state"
+              type='{"type": "string", "enum": ["set", ["VOID", "BACKOFF", 
"CONNECTING", "ACTIVE", "IDLE"]]}'>
+        <p>
+          The state of the connection to the manager:
+        </p>
+        <dl>
+          <dt><code>VOID</code></dt>
+          <dd>Connection is disabled.</dd>
+
+          <dt><code>BACKOFF</code></dt>
+          <dd>Attempting to reconnect at an increasing period.</dd>
+
+          <dt><code>CONNECTING</code></dt>
+          <dd>Attempting to connect.</dd>
+
+          <dt><code>ACTIVE</code></dt>
+          <dd>Connected, remote host responsive.</dd>
+
+          <dt><code>IDLE</code></dt>
+          <dd>Connection is idle.  Waiting for response to keep-alive.</dd>
+        </dl>
+        <p>
+          These values may change in the future.  They are provided only for
+          human consumption.
+        </p>
+      </column>
+
+      <column name="status" key="sec_since_connect"
+              type='{"type": "integer", "minInteger": 0}'>
+        The amount of time since this client last successfully connected
+        to the database (in seconds). Value is empty if client has never
+        successfully been connected.
+      </column>
+
+      <column name="status" key="sec_since_disconnect"
+              type='{"type": "integer", "minInteger": 0}'>
+        The amount of time since this client last disconnected from the
+        database (in seconds). Value is empty if client has never
+        disconnected.
+      </column>
+
+      <column name="status" key="locks_held">
+        Space-separated list of the names of OVSDB locks that the connection
+        holds.  Omitted if the connection does not hold any locks.
+      </column>
+
+      <column name="status" key="locks_waiting">
+        Space-separated list of the names of OVSDB locks that the connection is
+        currently waiting to acquire.  Omitted if the connection is not waiting
+        for any locks.
+      </column>
+
+      <column name="status" key="locks_lost">
+        Space-separated list of the names of OVSDB locks that the connection
+        has had stolen by another OVSDB client.  Omitted if no locks have been
+        stolen from this connection.
+      </column>
+
+      <column name="status" key="n_connections"
+              type='{"type": "integer", "minInteger": 2}'>
+        When <ref column="target"/> specifies a connection method that
+        listens for inbound connections (e.g. <code>ptcp:</code> or
+        <code>pssl:</code>) and more than one connection is actually active,
+        the value is the number of active connections.  Otherwise, this
+        key-value pair is omitted.
+      </column>
+
+      <column name="status" key="bound_port" type='{"type": "integer"}'>
+        When <ref column="target"/> is <code>ptcp:</code> or
+        <code>pssl:</code>, this is the TCP port on which the OVSDB server is
+        listening.  (This is particularly useful when <ref
+        column="target"/> specifies a port of 0, allowing the kernel to
+        choose any available port.)
+      </column>
+    </group>
+
+    <group title="Common Columns">
+      The overall purpose of these columns is described under <code>Common
+      Columns</code> at the beginning of this document.
+
+      <column name="external_ids"/>
+      <column name="other_config"/>
+    </group>
+  </table>
+
+  <table name="SSL">
+    SSL/TLS configuration for ovn-nb database access.
+
+    <column name="private_key">
+      Name of a PEM file containing the private key used as the switch's
+      identity for SSL/TLS connections to the controller.
+    </column>
+
+    <column name="certificate">
+      Name of a PEM file containing a certificate, signed by the
+      certificate authority (CA) used by the controller and manager,
+      that certifies the switch's private key, identifying a trustworthy
+      switch.
+    </column>
+
+    <column name="ca_cert">
+      Name of a PEM file containing the CA certificate used to verify
+      that the switch is connected to a trustworthy controller.
+    </column>
+
+    <column name="bootstrap_ca_cert">
+      If set to <code>true</code>, then Open vSwitch will attempt to
+      obtain the CA certificate from the controller on its first SSL/TLS
+      connection and save it to the named PEM file. If it is successful,
+      it will immediately drop the connection and reconnect, and from then
+      on all SSL/TLS connections must be authenticated by a certificate signed
+      by the CA certificate thus obtained.  <em>This option exposes the
+      SSL/TLS connection to a man-in-the-middle attack obtaining the initial
+      CA certificate.</em>  It may still be useful for bootstrapping.
+    </column>
+
+    <column name="ssl_protocols">
+      <p>
+        Range or a comma- or space-delimited list of the SSL/TLS protocols to
+        enable for SSL/TLS connections.
+      </p>
+      <p>
+        Supported protocols include <code>TLSv1.2</code> and
+        <code>TLSv1.3</code>.  Ranges can be provided in a form of two protocol
+        names separated with a dash (<code>TLSv1.2-TLSv1.3</code>), or as a
+        single protocol name with a plus sign (<code>TLSv1.2+</code>).  The
+        value can be a list of protocols or exactly one range.  The range is a
+        preferred way of specifying protocols and the configuration always
+        behaves as if the range between the minimum and the maximum specified
+        version is provided, i.e., if the value is set to
+        <code>TLSv1.X,TLSv1.(X+2)</code>, the <code>TLSv1.(X+1)</code> will
+        also be enabled as if it was a range.
+        Regardless of order, the highest protocol supported by both sides will
+        be chosen when making the connection.
+      </p>
+      <p>
+        The default when this option is omitted is <code>TLSv1.2+</code>.
+      </p>
+    </column>
+
+    <column name="ssl_ciphers">
+      List of ciphers (in OpenSSL cipher string format) to be supported
+      for SSL/TLS connections with TLSv1.2.  The default when this option
+      is omitted is <code>DEFAULT:@SECLEVEL=2</code>.
+    </column>
+
+    <column name="ssl_ciphersuites">
+      List of ciphersuites (in OpenSSL ciphersuites string format) to be
+      supported for SSL/TLS connections with TLSv1.3 and later.  Default value
+      from OpenSSL will be used when this option is omitted.
+    </column>
+
+    <group title="Common Columns">
+      The overall purpose of these columns is described under <code>Common
+      Columns</code> at the beginning of this document.
+
+      <column name="external_ids"/>
+    </group>
+  </table>
+
+  <table name="Bridge" title="OVS Bridge to control">
+    <column name="name">
+      Name of the OVS bridge.  This bridge should exist in the local OVS
+      database.
+    </column>
+
+    <column name="options">
+      Reserved for future use.
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+
+  <table name="Logical_Flow" title="Logical flow">
+    <column name="bridge">
+      The bridge to which the logical flow belongs to.
+    </column>
+
+    <column name="table_id">
+      The stage in the logical pipeline, analogous to an OpenFlow table number.
+    </column>
+
+    <column name="priority">
+      The flow's priority.  Flows with numerically higher priority take
+      precedence over those with lower.  If two logical flows with the
+      same priority both match, then the one actually applied to the packet is
+      undefined.
+    </column>
+
+    <column name="match">
+      <p>
+        A matching expression.  OVN provides a superset of OpenFlow matching
+        capabilities, using a syntax similar to Boolean expressions in a
+        programming language.
+      </p>
+
+      <p>
+        Please see the documentation of the
+        <ref
+        column="match" table="Logical_Flow" db="OVN_Southbound"/>
+        column of the <ref table="Logical_Flow"
+        db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database.
+      </p>
+    </column>
+
+    <column name="actions">
+      <p>
+        Logical datapath actions, to be executed when the logical flow
+        represented by this row is the highest-priority match.
+      </p>
+
+      <p>
+        Actions share lexical syntax with the <ref column="match"/> column.  An
+        empty set of actions (or one that contains just white space or
+        comments), or a set of actions that consists of just
+        <code>drop;</code>, causes the matched packets to be dropped.
+        Otherwise, the column should contain a sequence of actions, each
+        terminated by a semicolon.
+      </p>
+
+      <p>
+        Please see the documentation of the
+        <ref
+        column="actions" table="Logical_Flow" db="OVN_Southbound"/>
+        column of the <ref table="Logical_Flow"
+        db="OVN_Southbound"/> table in the <ref db="OVN_Southbound"/> database.
+      </p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+</database>
diff --git a/rhel/automake.mk b/rhel/automake.mk
index 445dcd2fd4..c4ebf89d8a 100644
--- a/rhel/automake.mk
+++ b/rhel/automake.mk
@@ -18,7 +18,9 @@ EXTRA_DIST += \
        rhel/usr_lib_systemd_system_ovn-northd.service \
        rhel/usr_lib_firewalld_services_ovn-central-firewall-service.xml \
        rhel/usr_lib_firewalld_services_ovn-host-firewall-service.xml \
-       rhel/usr_share_ovn_scripts_systemd_sysconfig.template
+       rhel/usr_share_ovn_scripts_systemd_sysconfig.template \
+       rhel/usr_lib_systemd_system_ovn-br-controller.service \
+       rhel/usr_lib_systemd_system_ovn-br-db.service
 
 update_rhel_spec = \
   $(AM_V_GEN)($(ro_shell) && sed -e 's,[@]VERSION[@],$(VERSION),g') \
diff --git a/rhel/ovn-fedora.spec.in b/rhel/ovn-fedora.spec.in
index 670f1ca9eb..a1ab46ebef 100644
--- a/rhel/ovn-fedora.spec.in
+++ b/rhel/ovn-fedora.spec.in
@@ -119,6 +119,14 @@ Provides: openvswitch-ovn-docker = 
%{?epoch:%{epoch}:}%{version}-%{release}
 %description docker
 Docker network plugins for OVN.
 
+%package br-controller
+Summary: Open Virtual Network support
+License: ASL 2.0
+Requires: ovn
+
+%description br-controller
+OVN bridge controller
+
 %prep
 %autosetup -n ovn-%{version} -a 10 -p 1
 
@@ -165,7 +173,7 @@ install -p -D -m 0644 \
         rhel/usr_share_ovn_scripts_systemd_sysconfig.template \
         $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/ovn
 
-for service in ovn-controller ovn-controller-vtep ovn-northd ovn-ic ovn-ic-db 
ovn-db@; do
+for service in ovn-controller ovn-controller-vtep ovn-northd ovn-ic ovn-ic-db 
ovn-db@ ovn-br-controller ovn-br-db; do
         install -p -D -m 0644 \
                         rhel/usr_lib_systemd_system_${service}.service \
                         $RPM_BUILD_ROOT%{_unitdir}/${service}.service
@@ -549,7 +557,19 @@ fi
 %{_mandir}/man8/ovn-controller-vtep.8*
 %{_unitdir}/ovn-controller-vtep.service
 
+%files br-controller
+%{_bindir}/ovn-br-controller
+%{_bindir}/ovn-brctl
+%{_mandir}/man8/ovn-br-controller.8*
+%{_mandir}/man5/ovn-br.5*
+%config %{_datadir}/ovn/ovn-br.ovsschema
+%{_unitdir}/ovn-br-controller.service
+%{_unitdir}/ovn-br-db.service
+
 %changelog
+* Wed Sep 24 2025 Numan Siddique <[email protected]>
+- Added ovn-br-controller systemd-units.
+
 * Wed Jan 11 2023 Vladislav Odintsov <[email protected]>
 - Added [email protected] systemd-unit.
 
diff --git a/rhel/usr_lib_systemd_system_ovn-br-controller.service 
b/rhel/usr_lib_systemd_system_ovn-br-controller.service
new file mode 100644
index 0000000000..9f1e3f4b81
--- /dev/null
+++ b/rhel/usr_lib_systemd_system_ovn-br-controller.service
@@ -0,0 +1,35 @@
+# See ovn-br-controller(8) for details about ovn-br-controller.
+#
+# To customize the ovn-br-controller service, you may create a configuration 
file
+# in the /etc/systemd/system/ovn-br-controller.d/ directory.  For example, to 
specify
+# additional options to be passed to the "ovn-ctl start_ovnbr_controller" 
command, you
+# could place the following contents in
+# /etc/systemd/system/ovn-br-controller.d/local.conf:
+#
+#   [System]
+#   Environment="OVNBR_CONTROLLER_OPTS=--ovnbr-controller-log=-vconsole:emer 
-vsyslog:err -vfile:info"
+#
+# Alternatively, you may specify environment variables in the file 
/etc/sysconfig/ovn-controller:
+#
+#   OVNBR_CONTROLLER_OPTS="--ovnbr-controller-log=-vconsole:emer -vsyslog:err 
-vfile:info"
+
+[Unit]
+Description=OVN Bridge controller daemon
+After=syslog.target
+Requires=openvswitch.service
+After=openvswitch.service
+
+[Service]
+Type=forking
+PIDFile=/var/run/ovn/ovn-br-controller.pid
+Restart=on-failure
+Environment=OVN_RUNDIR=%t/ovn OVS_RUNDIR=%t/openvswitch
+EnvironmentFile=-/etc/sysconfig/ovn
+EnvironmentFile=-/etc/sysconfig/ovn-br-controller
+ExecStart=/usr/share/ovn/scripts/ovn-ctl --no-monitor \
+           --ovn-user=${OVN_USER_ID} \
+          start_ovnbr_controller $OVNBR_CONTROLLER_OPTS
+ExecStop=/usr/share/ovn/scripts/ovn-ctl stop_ovnbr_controller
+
+[Install]
+WantedBy=multi-user.target
diff --git a/rhel/usr_lib_systemd_system_ovn-br-db.service 
b/rhel/usr_lib_systemd_system_ovn-br-db.service
new file mode 100644
index 0000000000..6de2a22f1e
--- /dev/null
+++ b/rhel/usr_lib_systemd_system_ovn-br-db.service
@@ -0,0 +1,32 @@
+# See ovn-br(8) for details about ovn-br.
+#
+# To customize the ovn-br-db service, you may create a configuration file
+# in the /etc/systemd/system/ovn-br-db.d/ directory.  For example, to specify
+# additional options to be passed to the "ovn-ctl start_ovnbr_ovsdb" command, 
you
+# could place the following contents in
+# /etc/systemd/system/ovn-br-db.d/local.conf:
+#
+#   [System]
+#   Environment="OVN_BR_DB_OPTS=--db-br-create-insecure-remote=yes"
+#
+# Alternatively, you may specify environment variables in the file 
/etc/sysconfig/ovn-br-db:
+#
+#   OVN_BR_DB_OPTS="--db-br-create-insecure-remote=yes"
+
+[Unit]
+Description=OVN Bridge Controller OVSDB server
+After=syslog.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+Environment=OVN_RUNDIR=%t/ovn OVN_DBDIR=/var/lib/ovn
+EnvironmentFile=-/etc/sysconfig/ovn
+EnvironmentFile=-/etc/sysconfig/ovn-br-db
+ExecStartPre=-/usr/bin/chown -R ${OVN_USER_ID} ${OVN_DBDIR}
+ExecStart=/usr/share/ovn/scripts/ovn-ctl \
+          --ovn-user=${OVN_USER_ID} start_ovnbr_ovsdb $OVN_BR_DB_OPTS
+ExecStop=/usr/share/ovn/scripts/ovn-ctl stop_ovnbr_ovsdb
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tutorial/ovn-sandbox b/tutorial/ovn-sandbox
index a689eb30f9..970afde5cd 100755
--- a/tutorial/ovn-sandbox
+++ b/tutorial/ovn-sandbox
@@ -64,6 +64,8 @@ gdb_ovn_controller=false
 gdb_ovn_controller_ex=false
 gdb_ovn_controller_vtep=false
 gdb_ovn_controller_vtep_ex=false
+gdb_ovn_br_controller=false
+gdb_ovn_br_controller_ex=false
 builddir=
 ovsbuilddir=
 srcdir=
@@ -90,6 +92,7 @@ ic_nb_servers=3
 ic_sb_model=clustered
 ic_sb_servers=3
 dummy=override
+ovn_br=false
 
 for option; do
     # This option-parsing mechanism borrowed from a Autoconf-generated
@@ -339,6 +342,9 @@ EOF
         --ic-sb-m*)
             prev=ic_sb_model
             ;;
+        --ovn-br)
+            ovn_br=true
+            ;;
         -R|--gdb-run)
             gdb_vswitchd_ex=true
             gdb_ovsdb_ex=true
@@ -422,6 +428,11 @@ if $built; then
         echo >&2 'source directory not found, please use --srcdir'
         exit 1
     fi
+    br_schema=$srcdir/ovn-br.ovsschema
+    if test ! -e "$br_schema"; then
+        echo >&2 'source directory not found, please use --srcdir'
+        exit 1
+    fi
 
     # Put built tools early in $PATH.
     if test ! -e $ovsbuilddir/vswitchd/ovs-vswitchd; then
@@ -429,7 +440,7 @@ if $built; then
         exit 1
     fi
     
PATH=$ovsbuilddir/ovsdb:$ovsbuilddir/vswitchd:$ovsbuilddir/utilities:$ovsbuilddir/vtep:$PATH
-    
PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$PATH
+    
PATH=$builddir/controller:$builddir/controller-vtep:$builddir/northd:$builddir/ic:$builddir/utilities:$builddir/br-controller:$PATH
     export PATH
 else
     case $schema in
@@ -616,6 +627,10 @@ ovn_start_db sb "$sbdb_model" "$sbdb_servers" 
"$ovnsb_source"
 ovn_start_db ic_nb "$ic_nb_model" "$ic_nb_servers" "$ic_nb_schema"
 ovn_start_db ic_sb "$ic_sb_model" "$ic_sb_servers" "$ic_sb_schema"
 
+if $ovn_br; then
+    ovn_start_db br standalone 1 "$br_schema"
+fi
+
 #Add a small delay to allow ovsdb-server to launch.
 sleep 0.1
 
@@ -692,6 +707,13 @@ rungdb $gdb_ovn_controller_vtep 
$gdb_ovn_controller_vtep_ex \
     $OVN_CTRLR_PKI --log-file -vsyslog:off \
     --ovnsb-db="$OVN_SB_DB"
 
+if $ovn_br; then
+    run ovs-vsctl set open . external-ids:ovn-br-remote=$OVN_BR_DB
+    rungdb $gdb_ovn_br_controller $gdb_ovn_br_controller_ex ovn-br-controller \
+        --detach --no-chdir -vsyslog:off \
+        --log-file=ovn-br-controller.log \
+        --pidfile=ovn-br-controller.pid -vconsole:off
+fi
 cat <<EOF
 
 
diff --git a/utilities/automake.mk b/utilities/automake.mk
index 79cf780ab9..b620038d01 100644
--- a/utilities/automake.mk
+++ b/utilities/automake.mk
@@ -118,4 +118,12 @@ bin_PROGRAMS += utilities/ovn-debug
 utilities_ovn_debug_SOURCES = utilities/ovn-debug.c
 utilities_ovn_debug_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la 
$(OVS_LIBDIR)/libopenvswitch.la
 
+# ovn-brctl
+bin_PROGRAMS += utilities/ovn-brctl
+utilities_ovn_brctl_SOURCES = \
+    utilities/ovn-dbctl.c \
+    utilities/ovn-dbctl.h \
+    utilities/ovn-brctl.c
+utilities_ovn_brctl_LDADD = lib/libovn.la $(OVSDB_LIBDIR)/libovsdb.la 
$(OVS_LIBDIR)/libopenvswitch.la
+
 include utilities/bugtool/automake.mk
diff --git a/utilities/ovn-brctl.c b/utilities/ovn-brctl.c
new file mode 100644
index 0000000000..60b62420a7
--- /dev/null
+++ b/utilities/ovn-brctl.c
@@ -0,0 +1,524 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ #include <config.h>
+
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* OVS includes. */
+#include "command-line.h"
+#include "daemon.h"
+#include "db-ctl-base.h"
+#include "dirs.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "lib/ovn-br-idl.h"
+#include "lib/ovn-dirs.h"
+#include "lib/ovn-util.h"
+#include "lib/vec.h"
+#include "ovn-dbctl.h"
+
+VLOG_DEFINE_THIS_MODULE(brctl);
+
+static char * OVS_WARN_UNUSED_RESULT br_by_name_or_uuid(
+    struct ctl_context *ctx, const char *id, bool must_exist,
+    const struct ovnbrrec_bridge **br_p);
+
+static void
+brctl_add_base_prerequisites(struct ovsdb_idl *idl,
+                             enum nbctl_wait_type wait_type OVS_UNUSED)
+{
+    ovsdb_idl_add_table(idl, &ovnbrrec_table_br_global);
+}
+
+static void
+brctl_pre_execute(struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
+                  enum nbctl_wait_type wait_type OVS_UNUSED)
+{
+    const struct ovnbrrec_br_global *br = ovnbrrec_br_global_first(idl);
+    if (!br) {
+        br = ovnbrrec_br_global_insert(txn);
+    }
+}
+
+static int
+get_inactivity_probe(struct ovsdb_idl *idl)
+{
+    const struct ovnbrrec_br_global *pr = ovnbrrec_br_global_first(idl);
+    int interval = DEFAULT_UTILS_PROBE_INTERVAL_MSEC;
+
+    if (pr) {
+        interval = smap_get_int(&pr->options, "brctl_probe_interval",
+                                interval);
+    }
+
+    return interval;
+}
+
+/* ovn-brctl specific context.  Inherits the 'struct ctl_context' as base. */
+struct brctl_context {
+    struct ctl_context base;
+};
+
+static struct ctl_context *
+brctl_ctx_create(void)
+{
+    struct brctl_context *prctx = xmalloc(sizeof *prctx);
+
+    return &prctx->base;
+}
+
+static void
+brctl_ctx_destroy(struct ctl_context *ctx)
+{
+    free(ctx);
+}
+
+static void
+print_br(const struct ovnbrrec_bridge *br, struct ds *s)
+{
+    ds_put_format(s, "bridge "UUID_FMT" (%s)\n",
+                  UUID_ARGS(&br->header_.uuid), br->name);
+}
+
+static void
+brctl_init(struct ctl_context *ctx OVS_UNUSED)
+{
+}
+
+static void
+brctl_pre_show(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids);
+}
+
+static const struct ctl_table_class tables[OVNBRREC_N_TABLES] = {
+    [OVNBRREC_TABLE_BRIDGE].row_ids[0]
+    = {&ovnbrrec_bridge_col_name, NULL, NULL},
+
+    [OVNBRREC_TABLE_CONNECTION].row_ids[0]
+    = {&ovnbrrec_connection_col_target, NULL, NULL},
+};
+
+static void
+brctl_show(struct ctl_context *ctx)
+{
+    const struct ovnbrrec_bridge *br;
+
+    if (ctx->argc == 2) {
+        char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &br);
+        if (error) {
+            ctx->error = error;
+            return;
+        }
+
+        print_br(br, &ctx->output);
+    } else {
+        OVNBRREC_BRIDGE_FOR_EACH (br, ctx->idl) {
+            print_br(br, &ctx->output);
+        }
+    }
+}
+
+static void
+pre_get_info(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_name);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_options);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_bridge_col_external_ids);
+
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_actions);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_bridge);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_match);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_priority);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_table_id);
+    ovsdb_idl_add_column(ctx->idl, &ovnbrrec_logical_flow_col_external_ids);
+}
+
+struct brctl_lflow {
+    const struct ovnbrrec_logical_flow *lflow;
+    const struct ovnbrrec_bridge *br;
+};
+
+static int
+brctl_lflow_cmp(const void *a_, const void *b_)
+{
+    const struct brctl_lflow *a_ctl_lflow = a_;
+    const struct brctl_lflow *b_ctl_lflow = b_;
+
+    const struct ovnbrrec_logical_flow *a = a_ctl_lflow->lflow;
+    const struct ovnbrrec_logical_flow *b = b_ctl_lflow->lflow;
+
+    const struct ovnbrrec_bridge *abr = a_ctl_lflow->br;
+    const struct ovnbrrec_bridge *bbr = b_ctl_lflow->br;
+    const char *a_name = abr->name;
+    const char *b_name = bbr->name;
+    int cmp = strcmp(a_name, b_name);
+    if (cmp) {
+        return cmp;
+    }
+
+    cmp = uuid_compare_3way(&abr->header_.uuid, &bbr->header_.uuid);
+    if (cmp) {
+        return cmp;
+    }
+
+    cmp = (a->table_id > b->table_id ? 1
+           : a->table_id < b->table_id ? -1
+           : a->priority > b->priority ? -1
+           : a->priority < b->priority ? 1
+           : strcmp(a->match, b->match));
+    return cmp ? cmp : strcmp(a->actions, b->actions);
+}
+
+static void
+brctl_lflow_add(struct vector *lflows,
+                const struct ovnbrrec_logical_flow *lflow,
+                const struct ovnbrrec_bridge *br)
+{
+    struct brctl_lflow brctl_lflow = (struct brctl_lflow) {
+        .lflow = lflow,
+        .br = br,
+    };
+    vector_push(lflows, &brctl_lflow);
+}
+
+static void
+print_uuid_part(const struct uuid *uuid, bool do_print, struct ds *s)
+{
+    if (!do_print) {
+        return;
+    }
+    ds_put_format(s, "uuid=0x%08"PRIx32", ", uuid->parts[0]);
+}
+
+static void
+print_bridge_prompt(const struct ovnbrrec_bridge *br,
+                    const struct uuid *uuid, struct ds *s) {
+        ds_put_format(s, "Bridge: %s", br->name);
+        ds_put_format(s, " ("UUID_FMT")\n",
+                      UUID_ARGS(uuid));
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_priority(const char *arg, int64_t *priority_p)
+{
+    /* Validate priority. */
+    int64_t priority;
+    if (!ovs_scan(arg, "%"SCNd64, &priority)
+        || priority < 0 || priority > 32767) {
+        /* Priority_p could be uninitialized as no valid priority was
+         * input, initialize it to a valid value of 0 before returning */
+        *priority_p = 0;
+        return xasprintf("%s: priority must in range 0...32767", arg);
+    }
+    *priority_p = priority;
+    return NULL;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_table_id(const char *arg, int64_t *table_p)
+{
+    /* Validate table id. */
+    int64_t table;
+    if (!ovs_scan(arg, "%"SCNd64, &table)
+        || table < 0 || table > 55) {
+        /* table_p could be uninitialized as no valid table id was
+         * input, initialize it to a valid value of 0 before returning */
+        *table_p = 0;
+        return xasprintf("%s: table must in range 0...55", arg);
+    }
+    *table_p = table;
+    return NULL;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+br_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist,
+                   const struct ovnbrrec_bridge **br_p)
+{
+    const struct ovsdb_idl_row *row = NULL;
+
+    char *error = ctl_get_row(ctx, &ovnbrrec_table_bridge,
+                              id, must_exist, &row);
+    *br_p = (const struct ovnbrrec_bridge *) row;
+    return error;
+}
+
+static void
+cmd_br_add(struct ctl_context *ctx)
+{
+    const struct ovnbrrec_bridge *bridge = NULL;
+
+    char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge);
+    if (error) {
+        ctl_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+
+    if (bridge) {
+        ctl_error(ctx, "Bridge %s already exists", ctx->argv[1]);
+    }
+
+    bridge = ovnbrrec_bridge_insert(ctx->txn);
+    ovnbrrec_bridge_set_name(bridge, ctx->argv[1]);
+}
+
+static void
+cmd_br_del(struct ctl_context *ctx)
+{
+    const struct ovnbrrec_bridge *bridge = NULL;
+
+    char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge);
+    if (error) {
+        ctl_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+
+    ovnbrrec_bridge_delete(bridge);
+}
+
+static void
+cmd_lflow_list(struct ctl_context *ctx)
+{
+    struct vector lflows = VECTOR_EMPTY_INITIALIZER(struct brctl_lflow);
+    const struct ovnbrrec_bridge *bridge = NULL;
+
+    if (ctx->argc > 1) {
+        char *error = br_by_name_or_uuid(ctx, ctx->argv[1], false, &bridge);
+        if (error) {
+            ctl_error(ctx, "%s", error);
+            free(error);
+            return;
+        }
+
+        if (bridge) {
+            ctx->argc--;
+            ctx->argv++;
+        }
+    }
+
+    const struct ovnbrrec_logical_flow *lflow;
+    OVNBRREC_LOGICAL_FLOW_FOR_EACH (lflow, ctx->idl) {
+        if (bridge && lflow->bridge != bridge) {
+            continue;
+        }
+
+        brctl_lflow_add(&lflows, lflow, lflow->bridge);
+    }
+
+    vector_qsort(&lflows, brctl_lflow_cmp);
+
+    bool print_uuid = shash_find(&ctx->options, "--uuid") != NULL;
+
+    const struct brctl_lflow *curr, *prev = NULL;
+    VECTOR_FOR_EACH_PTR (&lflows, curr) {
+        /* Figure out whether to print this particular flow.  By default, we
+         * print all flows, but if any UUIDs were listed on the command line
+         * then we only print the matching ones. */
+        bool include;
+        if (ctx->argc > 1) {
+            include = false;
+            for (size_t j = 1; j < ctx->argc; j++) {
+                if (is_partial_uuid_match(&curr->lflow->header_.uuid,
+                                          ctx->argv[j])) {
+                    include = true;
+                    break;
+                }
+            }
+        } else {
+            include = true;
+        }
+        if (!include) {
+            continue;
+        }
+
+        /* Print a header line for this datapath or pipeline, if we haven't
+         * already done so. */
+        if (!prev || prev->br != curr->br) {
+               print_bridge_prompt(curr->br, &curr->br->header_.uuid,
+                                     &ctx->output);
+        }
+
+        /* Print the flow. */
+        ds_put_cstr(&ctx->output, "  ");
+        print_uuid_part(&curr->lflow->header_.uuid, print_uuid, &ctx->output);
+        ds_put_format(&ctx->output,
+                      "table=%-2"PRId64", priority=%-5"PRId64
+                      ", match=(%s), action=(%s)\n",
+                      curr->lflow->table_id,
+                      curr->lflow->priority, curr->lflow->match,
+                      curr->lflow->actions);
+        prev = curr;
+    }
+
+    vector_destroy(&lflows);
+}
+
+static void
+cmd_lflow_add(struct ctl_context *ctx)
+{
+    const struct ovnbrrec_bridge *bridge = NULL;
+
+    char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge);
+    if (error) {
+        ctl_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+
+    int64_t table_id;
+    error = parse_table_id(ctx->argv[2], &table_id);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    int64_t priority;
+    error = parse_priority(ctx->argv[3], &priority);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    struct ovnbrrec_logical_flow *lflow =
+        ovnbrrec_logical_flow_insert(ctx->txn);
+    ovnbrrec_logical_flow_set_bridge(lflow, bridge);
+    ovnbrrec_logical_flow_set_table_id(lflow, table_id);
+    ovnbrrec_logical_flow_set_priority(lflow, priority);
+    ovnbrrec_logical_flow_set_match(lflow, ctx->argv[4]);
+    ovnbrrec_logical_flow_set_actions(lflow, ctx->argv[5]);
+}
+
+static void
+cmd_lflow_del(struct ctl_context *ctx)
+{
+    const struct ovnbrrec_logical_flow *lflow;
+    const struct ovsdb_idl_row *row;
+
+    char *error = ctl_get_row(ctx, &ovnbrrec_table_logical_flow,
+                              ctx->argv[1], true, &row);
+    if (error) {
+        ctl_error(ctx, "%s", error);
+        free(error);
+        return;
+    }
+
+    lflow = (const struct ovnbrrec_logical_flow *) row;
+    ovnbrrec_logical_flow_delete(lflow);
+}
+
+static void
+cmd_lflows_del(struct ctl_context *ctx)
+{
+    const struct ovnbrrec_bridge *bridge = NULL;
+
+    if (ctx->argc > 1) {
+        char *error = br_by_name_or_uuid(ctx, ctx->argv[1], true, &bridge);
+        if (error) {
+            ctl_error(ctx, "%s", error);
+            free(error);
+            return;
+        }
+    }
+
+    const struct ovnbrrec_logical_flow *lflow;
+    OVNBRREC_LOGICAL_FLOW_FOR_EACH_SAFE (lflow, ctx->idl) {
+        if (!bridge || lflow->bridge == bridge) {
+            ovnbrrec_logical_flow_delete(lflow);
+        }
+    }
+}
+
+static const struct ctl_command_syntax brctl_commands[] = {
+    { "init", 0, 0, "", NULL, brctl_init, NULL, "", RW },
+    { "show", 0, 1, "[BRIDGE]", brctl_pre_show, brctl_show, NULL, "", RO },
+
+    /* Bridge commands. */
+    {"add-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_add, NULL,
+     "", RW},
+    {"del-br", 1, 1, "BRIDGE", pre_get_info, cmd_br_del, NULL,
+     "", RW},
+
+    /* Logical flow commands */
+    {"lflow-list", 0, INT_MAX, "[BRIDGE] [LFLOW...]",
+     pre_get_info, cmd_lflow_list, NULL,
+     "--uuid,--ovs?,--stats,--vflows?", RO},
+    {"dump-flows", 0, INT_MAX, "[DATAPATH] [LFLOW...]",
+     pre_get_info, cmd_lflow_list, NULL,
+     "--uuid,--ovs?,--stats,--vflows?",
+     RO}, /* Friendly alias for lflow-list */
+    {"add-flow", 5, 5, "BRIDGE TABLE PRIORITY MATCH ACTION",
+     pre_get_info, cmd_lflow_add, NULL,
+     "", RW},
+     {"del-flow", 1, 1, "UUID", pre_get_info, cmd_lflow_del, NULL,
+     "", RW},
+     {"del-flows", 0, 1, "[BRIDGE]", pre_get_info, cmd_lflows_del, NULL,
+     "", RW},
+     {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
+};
+
+static void
+brctl_usage(void)
+{
+    printf("\
+%s: OVN Provider DB management utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+General commands:\n\
+  init                      initialize the database\n\
+  show                      print overview of database contents\n\
+\n\
+Logical flow commands:\n\
+  lflow-list  [BRIDGE] [LFLOW...] list logical flows for BRIDGE\n\
+  dump-flows  [BRIDGE] [LFLOW...] alias for lflow-list\n\
+\n\
+\n\n", program_name, program_name);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct ovn_dbctl_options dbctl_options = {
+        .db_version = ovnbrrec_get_db_version(),
+        .default_db = default_br_db(),
+        .allow_wait = false,
+
+        .options_env_var_name = "OVN_brctl_OPTIONS",
+        .daemon_env_var_name = "OVN_PR_DAEMON",
+
+        .idl_class = &ovnbrrec_idl_class,
+        .tables = tables,
+        .cmd_show_table = NULL,
+        .commands = brctl_commands,
+
+        .usage = brctl_usage,
+        .add_base_prerequisites = brctl_add_base_prerequisites,
+        .pre_execute = brctl_pre_execute,
+        .post_execute = NULL,
+        .get_inactivity_probe = get_inactivity_probe,
+
+        .ctx_create = brctl_ctx_create,
+        .ctx_destroy = brctl_ctx_destroy,
+    };
+
+    return ovn_dbctl_main(argc, argv, &dbctl_options);
+}
diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
index 0bba8f6889..c06bc435c4 100644
--- a/utilities/ovn-sbctl.c
+++ b/utilities/ovn-sbctl.c
@@ -624,12 +624,6 @@ sbctl_lflow_cmp(const void *a_, const void *b_)
     return cmp ? cmp : strcmp(a->actions, b->actions);
 }
 
-static bool
-is_uuid_with_prefix(const char *uuid)
-{
-     return uuid[0] == '0' && (uuid[1] == 'x' || uuid[1] == 'X');
-}
-
 static bool
 parse_partial_uuid(char *s)
 {
@@ -648,30 +642,6 @@ parse_partial_uuid(char *s)
     return false;
 }
 
-static const char *
-strip_leading_zero(const char *s)
-{
-    return s + strspn(s, "0");
-}
-
-static bool
-is_partial_uuid_match(const struct uuid *uuid, const char *match)
-{
-    char uuid_s[UUID_LEN + 1];
-    snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(uuid));
-
-    /* We strip leading zeros because we want to accept cookie values derived
-     * from UUIDs, and cookie values are printed without leading zeros because
-     * they're just numbers. */
-    const char *s1 = strip_leading_zero(uuid_s);
-    const char *s2 = match;
-    if (is_uuid_with_prefix(s2)) {
-        s2 = s2 + 2;
-    }
-    s2 = strip_leading_zero(s2);
-    return !strncmp(s1, s2, strlen(s2));
-}
-
 static char *
 default_ovs(void)
 {
-- 
2.51.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to