diff --git a/Makefile.am b/Makefile.am
index 45fce1243..987bc89ca 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -163,6 +163,7 @@ SUFFIXES += .in
 	    -e 's,[@]PYTHON3[@],$(PYTHON3),g' \
 	    -e 's,[@]RUNDIR[@],$(RUNDIR),g' \
 	    -e 's,[@]VERSION[@],$(VERSION),g' \
+	    -e 's,[@]VERSION_SUFFIX[@],$(VERSION_SUFFIX),g' \
 	    -e 's,[@]localstatedir[@],$(localstatedir),g' \
 	    -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
 	    -e 's,[@]sysconfdir[@],$(sysconfdir),g' \
diff --git a/acinclude.m4 b/acinclude.m4
index f1ba046c2..f7a81a734 100644
--- a/acinclude.m4
+++ b/acinclude.m4
@@ -497,6 +497,19 @@ AC_DEFUN([OVS_CHECK_DPDK], [
   AM_CONDITIONAL([DPDK_NETDEV], test "$DPDKLIB_FOUND" = true)
 ])
 
+dnl Append a version suffix
+
+AC_DEFUN([OVS_CHECK_VERSION_SUFFIX], [
+  AC_ARG_WITH([version-suffix],
+              [AS_HELP_STRING([--with-version-suffix=ver_suffix],
+                              [Specify a version suffix that will be appended
+                               to OVS version])])
+  AC_DEFINE_UNQUOTED([VERSION_SUFFIX], ["$with_version_suffix"],
+                     [Package version suffix])
+  AC_SUBST([VERSION_SUFFIX], [$with_version_suffix])
+  ])
+])
+
 dnl Checks for net/if_dl.h.
 dnl
 dnl (We use this as a proxy for checking whether we're building on FreeBSD
diff --git a/configure.ac b/configure.ac
index dd6553fea..8323e481d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -202,6 +202,7 @@ OVS_CHECK_LINUX_SCTP_CT
 OVS_CHECK_LINUX_VIRTIO_TYPES
 OVS_CHECK_DPDK
 OVS_CHECK_PRAGMA_MESSAGE
+OVS_CHECK_VERSION_SUFFIX
 AC_SUBST([CFLAGS])
 AC_SUBST([OVS_CFLAGS])
 AC_SUBST([OVS_LDFLAGS])
diff --git a/include/openvswitch/version.h.in b/include/openvswitch/version.h.in
index 23d8fde4f..231f61e30 100644
--- a/include/openvswitch/version.h.in
+++ b/include/openvswitch/version.h.in
@@ -19,7 +19,7 @@
 #define OPENVSWITCH_VERSION_H 1
 
 #define OVS_PACKAGE_STRING  "@PACKAGE_STRING@"
-#define OVS_PACKAGE_VERSION "@PACKAGE_VERSION@"
+#define OVS_PACKAGE_VERSION "@PACKAGE_VERSION@@VERSION_SUFFIX@"
 
 #define OVS_LIB_VERSION     @LT_CURRENT@
 #define OVS_LIB_REVISION    @LT_REVISION@
diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c
index 9ad42b232..56512fc28 100644
--- a/lib/ovsdb-error.c
+++ b/lib/ovsdb-error.c
@@ -146,7 +146,7 @@ ovsdb_internal_error(struct ovsdb_error *inner_error,
         ds_put_char(&ds, ')');
     }
 
-    ds_put_format(&ds, " (%s %s)", program_name, VERSION);
+    ds_put_format(&ds, " (%s %s)", program_name, VERSION VERSION_SUFFIX);
 
     if (inner_error) {
         char *s = ovsdb_error_to_string_free(inner_error);
diff --git a/lib/util.c b/lib/util.c
index 3fb3a4b40..c1bfda0e6 100644
--- a/lib/util.c
+++ b/lib/util.c
@@ -617,8 +617,9 @@ ovs_set_program_name(const char *argv0, const char *version)
     program_name = basename;
 
     free(program_version);
-    if (!strcmp(version, VERSION)) {
-        program_version = xasprintf("%s (Open vSwitch) "VERSION"\n",
+    if (!strcmp(version, VERSION VERSION_SUFFIX)) {
+        program_version = xasprintf("%s (Open vSwitch) "VERSION
+                                    VERSION_SUFFIX"\n",
                                     program_name);
     } else {
         program_version = xasprintf("%s %s\n"
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
index b51fd42fe..a876f8bcf 100644
--- a/ovsdb/ovsdb-server.c
+++ b/ovsdb/ovsdb-server.c
@@ -816,7 +816,8 @@ main(int argc, char *argv[])
         /* ovsdb-server is usually a long-running process, in which case it
          * makes plenty of sense to log the version, but --run makes
          * ovsdb-server more like a command-line tool, so skip it.  */
-        VLOG_INFO("%s (Open vSwitch) %s", program_name, VERSION);
+        VLOG_INFO("%s (Open vSwitch) %s", program_name,
+                  VERSION VERSION_SUFFIX);
     }
 
     unixctl_command_register("exit", "", 0, 0, ovsdb_server_exit, &exiting);
diff --git a/python/.gitignore b/python/.gitignore
index 60ace6f05..ad5486af8 100644
--- a/python/.gitignore
+++ b/python/.gitignore
@@ -1,2 +1,3 @@
 dist/
 *.egg-info
+setup.py
diff --git a/python/automake.mk b/python/automake.mk
index 84cf2eab5..a5df8dbee 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -129,7 +129,7 @@ ovs-uninstall-local:
 ALL_LOCAL += $(srcdir)/python/ovs/version.py
 $(srcdir)/python/ovs/version.py: config.status
 	$(AM_V_GEN)$(ro_shell) > $(@F).tmp && \
-	echo 'VERSION = "$(VERSION)"' >> $(@F).tmp && \
+	echo 'VERSION = "$(VERSION)$(VERSION_SUFFIX)"' >> $(@F).tmp && \
 	if cmp -s $(@F).tmp $@; then touch $@; rm $(@F).tmp; else mv $(@F).tmp $@; fi
 
 ALL_LOCAL += $(srcdir)/python/ovs/dirs.py
@@ -147,6 +147,15 @@ $(srcdir)/python/ovs/dirs.py: python/ovs/dirs.py.template
 EXTRA_DIST += python/ovs/dirs.py.template
 CLEANFILES += python/ovs/dirs.py
 
+ALL_LOCAL += $(srcdir)/python/setup.py
+$(srcdir)/python/setup.py: python/setup.py.template
+	$(AM_V_GEN)sed \
+		-e 's,[@]VERSION[@],$(VERSION),g' \
+		< $? > $@.tmp && \
+	mv $@.tmp $@
+EXTRA_DIST += python/setup.py.template
+CLEANFILES += python/setup.py
+
 EXTRA_DIST += python/TODO.rst
 
 $(srcdir)/python/ovs/flow/ofp_fields.py: $(srcdir)/build-aux/gen_ofp_field_decoders include/openvswitch/meta-flow.h
diff --git a/python/setup.py b/python/setup.py.template
similarity index 95%
rename from python/setup.py
rename to python/setup.py.template
index bcf832ce9..3ddc11b70 100644
--- a/python/setup.py
+++ b/python/setup.py.template
@@ -23,19 +23,18 @@ except ImportError:  # Needed for setuptools < 59.0
 
 import setuptools
 
-VERSION = "unknown"
+VERSION = "@VERSION@"
 
 try:
-    # Try to set the version from the generated ovs/version.py
-    exec(open("ovs/version.py").read())
+    # Try to open generated ovs/version.py
+    open("ovs/version.py")
 except IOError:
     print("Ensure version.py is created by running make python/ovs/version.py",
           file=sys.stderr)
     sys.exit(-1)
 
 try:
-    # Try to open generated ovs/dirs.py. However, in this case we
-    # don't need to exec()
+    # Try to open generated ovs/dirs.py
     open("ovs/dirs.py")
 except IOError:
     print("Ensure dirs.py is created by running make python/ovs/dirs.py",
diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in
index 94b6d7431..f129bc646 100644
--- a/rhel/openvswitch-fedora.spec.in
+++ b/rhel/openvswitch-fedora.spec.in
@@ -186,6 +186,7 @@ This package provides IPsec tunneling support for OVS tunnels.
         --disable-static \
         --enable-shared \
         --with-pkidir=%{_sharedstatedir}/openvswitch/pki \
+        --with-version-suffix=-%{release} \
         PYTHON3=%{__python3}
 
 build-aux/dpdkstrip.py \
diff --git a/utilities/ovs-dpctl-top.in b/utilities/ovs-dpctl-top.in
index 2c1766eff..ec57eccd6 100755
--- a/utilities/ovs-dpctl-top.in
+++ b/utilities/ovs-dpctl-top.in
@@ -351,7 +351,7 @@ def args_get():
     # None is a special value indicating to read flows from stdin.
     # This handles the case
     #   ovs-dpctl dump-flows | ovs-dpctl-flows.py
-    parser.add_argument("-v", "--version", version="@VERSION@",
+    parser.add_argument("-v", "--version", version="@VERSION@@VERSION_SUFFIX@",
                         action="version", help="show version")
     parser.add_argument("-f", "--flow-file", dest="flowFiles", default=None,
                         action="append",
diff --git a/utilities/ovs-lib.in b/utilities/ovs-lib.in
index 7812a94ee..d162227dc 100644
--- a/utilities/ovs-lib.in
+++ b/utilities/ovs-lib.in
@@ -70,7 +70,7 @@ ovs_ctl () {
     esac
 }
 
-VERSION='@VERSION@'
+VERSION='@VERSION@@VERSION_SUFFIX@'
 
 DAEMON_CWD=/
 
diff --git a/utilities/ovs-parse-backtrace.in b/utilities/ovs-parse-backtrace.in
index f44f05cd1..42f831eed 100755
--- a/utilities/ovs-parse-backtrace.in
+++ b/utilities/ovs-parse-backtrace.in
@@ -51,7 +51,7 @@ def addr2line(binary, addr):
 
 
 def main():
-    parser = optparse.OptionParser(version='@VERSION@',
+    parser = optparse.OptionParser(version='@VERSION@@VERSION_SUFFIX@',
                                    usage="usage: %prog [binary]",
                                    description="""\
 Parses the output of ovs-appctl backtrace producing a more human readable
diff --git a/utilities/ovs-pcap.in b/utilities/ovs-pcap.in
index 6b5f63399..d0ca94788 100755
--- a/utilities/ovs-pcap.in
+++ b/utilities/ovs-pcap.in
@@ -85,7 +85,7 @@ if __name__ == "__main__":
             if key in ['-h', '--help']:
                 usage()
             elif key in ['-V', '--version']:
-                print("ovs-pcap (Open vSwitch) @VERSION@")
+                print("ovs-pcap (Open vSwitch) @VERSION@@VERSION_SUFFIX@")
             else:
                 sys.exit(0)
 
diff --git a/utilities/ovs-pki.in b/utilities/ovs-pki.in
index 3d2ef911c..69060b4ac 100755
--- a/utilities/ovs-pki.in
+++ b/utilities/ovs-pki.in
@@ -189,7 +189,7 @@ EOF
             exit 0
             ;;
         -V|--version)
-            echo "ovs-pki (Open vSwitch) @VERSION@"
+            echo "ovs-pki (Open vSwitch) @VERSION@@VERSION_SUFFIX@"
             exit 0
             ;;
         --di*=*)
diff --git a/utilities/ovs-tcpdump.in b/utilities/ovs-tcpdump.in
index eada803bb..cb46e43ba 100755
--- a/utilities/ovs-tcpdump.in
+++ b/utilities/ovs-tcpdump.in
@@ -47,7 +47,7 @@ try:
     from ovs.fatal_signal import add_hook
 except Exception:
     print("ERROR: Please install the correct Open vSwitch python support")
-    print("       libraries (version @VERSION@).")
+    print("       libraries (version @VERSION@@VERSION_SUFFIX@).")
     print("       Alternatively, check that your PYTHONPATH is pointing to")
     print("       the correct location.")
     sys.exit(1)
@@ -453,7 +453,7 @@ def main():
         if cur in ['-h', '--help']:
             usage()
         elif cur in ['-V', '--version']:
-            print("ovs-tcpdump (Open vSwitch) @VERSION@")
+            print("ovs-tcpdump (Open vSwitch) @VERSION@@VERSION_SUFFIX@")
             sys.exit(0)
         elif cur in ['--db-sock']:
             db_sock = nxt
diff --git a/utilities/ovs-tcpundump.in b/utilities/ovs-tcpundump.in
index ede5448b4..2a1b08332 100755
--- a/utilities/ovs-tcpundump.in
+++ b/utilities/ovs-tcpundump.in
@@ -46,7 +46,7 @@ if __name__ == "__main__":
         if key in ['-h', '--help']:
             usage()
         elif key in ['-V', '--version']:
-            print("ovs-tcpundump (Open vSwitch) @VERSION@")
+            print("ovs-tcpundump (Open vSwitch) @VERSION@@VERSION_SUFFIX@")
             sys.exit(0)
         else:
             sys.exit(0)
diff --git a/utilities/ovs-vlan-test.in b/utilities/ovs-vlan-test.in
index de3ae1686..3c15e2b13 100755
--- a/utilities/ovs-vlan-test.in
+++ b/utilities/ovs-vlan-test.in
@@ -393,7 +393,7 @@ def main():
             usage()
             return 0
         elif key in ['-V', '--version']:
-            print_safe('ovs-vlan-test (Open vSwitch) @VERSION@')
+            print_safe('ovs-vlan-test (Open vSwitch) @VERSION@@VERSION_SUFFIX@')
             return 0
         elif key in ['-s', '--server']:
             server = True
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index 95a65fcdc..0352030fe 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -3398,7 +3398,8 @@ bridge_run(void)
 
             vlog_enable_async();
 
-            VLOG_INFO_ONCE("%s (Open vSwitch) %s", program_name, VERSION);
+            VLOG_INFO_ONCE("%s (Open vSwitch) %s", program_name,
+                           VERSION VERSION_SUFFIX);
         }
     }
 
