On 10/15/25 11:28 PM, Gurucharan Shetty wrote:
> This commit introduces Server Name Indication (SNI) support for all OVS
> SSL/TLS clients, enabling clients to specify which hostname they are
> attempting to reach during the SSL handshake. This is essential for
> connecting through proxies, load balancers, and service meshes where
> the connection endpoint differs from the intended server name.
>
> An example use case is trying to connect to a ovsdb-server through
> a istio proxy gateway in kubernetes environments
>
> Signed-off-by: Gurucharan Shetty <[email protected]>
> ---
> NEWS | 6 +++++
> lib/ssl-syn.man | 2 ++
> lib/ssl.man | 7 ++++++
> lib/ssl.xml | 9 +++++++
> lib/stream-nossl.c | 7 ++++++
> lib/stream-ssl.c | 21 ++++++++++++++++-
> lib/stream-ssl.h | 13 +++++++---
> lib/stream.c | 4 +++-
> ovsdb/ovsdb-server.c | 9 +++++++
> tests/ovs-vsctl.at | 56 ++++++++++++++++++++++++++++++++++++++++++++
> 10 files changed, 129 insertions(+), 5 deletions(-)
>
> diff --git a/NEWS b/NEWS
> index 96bf4992c..d25be10db 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -1,5 +1,11 @@
> Post-v3.6.0
> --------------------
> + - TLS:
> + * Added support for TLS Server Name Indication (SNI) with the new
> + --ssl-server-name option. This allows specifying the server name
> + for SNI, which is useful when connecting through proxies or service
> + meshes where the connection endpoint differs from the intended
> + server name.
> - Userspace datapath:
> * Conntrack now supports the FTP commands EPSV and EPRT with IPv4
> connections, instead of limiting these commands to IPv6 only.
> diff --git a/lib/ssl-syn.man b/lib/ssl-syn.man
> index 583454548..77682bade 100644
> --- a/lib/ssl-syn.man
> +++ b/lib/ssl-syn.man
> @@ -4,3 +4,5 @@
> [\fB\-\-certificate=\fIcert.pem\fR]
> .br
> [\fB\-\-ca\-cert=\fIcacert.pem\fR]
> +.br
> +[\fB\-\-ssl\-server\-name=\fIservername\fR]
> diff --git a/lib/ssl.man b/lib/ssl.man
> index 9bec3a786..303f0550b 100644
> --- a/lib/ssl.man
> +++ b/lib/ssl.man
> @@ -24,3 +24,10 @@ be a different one, depending on the PKI design in use.)
> Disables verification of certificates presented by SSL/TLS peers. This
> introduces a security risk, because it means that certificates cannot
> be verified to be those of known trusted hosts.
> +.
> +.IP "\fB\-\-ssl\-server\-name=\fIservername\fR"
> +Specifies the server name to use for TLS Server Name Indication (SNI).
> +By default, the hostname from the connection string is used for SNI.
> +This option allows overriding the SNI hostname, which is useful when
> +connecting through proxies or service meshes where the connection endpoint
> +differs from the intended server name.
> diff --git a/lib/ssl.xml b/lib/ssl.xml
> index bd2502898..4dd2938a7 100644
> --- a/lib/ssl.xml
> +++ b/lib/ssl.xml
> @@ -33,4 +33,13 @@
> introduces a security risk, because it means that certificates cannot
> be verified to be those of known trusted hosts.
> </dd>
> +
> + <dt><code>--ssl-server-name=</code><var>servername</var></dt>
> + <dd>
> + Specifies the server name to use for TLS Server Name Indication (SNI).
> + By default, the hostname from the connection string is used for SNI.
> + This option allows overriding the SNI hostname, which is useful when
> + connecting through proxies or service meshes where the connection
> endpoint
> + differs from the intended server name.
> + </dd>
> </dl>
> diff --git a/lib/stream-nossl.c b/lib/stream-nossl.c
> index 105ac377a..c0a6bf92a 100644
> --- a/lib/stream-nossl.c
> +++ b/lib/stream-nossl.c
> @@ -96,3 +96,10 @@ stream_ssl_set_ciphersuites(const char *arg OVS_UNUSED)
> /* Ignore this option since it seems harmless to set TLS ciphersuites if
> * SSL/TLS won't be used. */
> }
> +
> +void
> +stream_ssl_set_server_name(const char *server_name OVS_UNUSED)
> +{
> + /* Ignore this option since it seems harmless to set TLS server name if
> + * SSL/TLS won't be used. */
> +}
> diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c
> index 99c82b6af..c647716ab 100644
> --- a/lib/stream-ssl.c
> +++ b/lib/stream-ssl.c
> @@ -168,6 +168,11 @@ static char *ssl_protocols = "TLSv1.2+";
> static char *ssl_ciphers = "DEFAULT:@SECLEVEL=2";
> static char *ssl_ciphersuites = ""; /* Using default ones, unless specified.
> */
>
> +/* Server name override for SNI (Server Name Indication).
> + * If set, this name will be used for SNI instead of the hostname
> + * extracted from the connection string. */
> +static char *ssl_server_name_override;
> +
> /* Ordinarily, the SSL client and server verify each other's certificates
> using
> * a CA certificate. Setting this to false disables this behavior. (This
> is a
> * security risk.) */
> @@ -372,7 +377,11 @@ ssl_open(const char *name, char *suffix, struct stream
> **streamp, uint8_t dscp)
> dscp);
> if (fd >= 0) {
> int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING;
> - return new_ssl_stream(xstrdup(name), get_server_name(suffix),
> + char *server_name = ssl_server_name_override
> + ? xstrdup(ssl_server_name_override)
> + : get_server_name(suffix);
nit: Above two lines should be 1 space to the right.
> +
> + return new_ssl_stream(xstrdup(name), server_name,
> fd, CLIENT, state, streamp);
> } else {
> VLOG_ERR("%s: connect: %s", name, ovs_strerror(error));
> @@ -1251,6 +1260,16 @@ stream_ssl_set_ciphersuites(const char *arg)
> ssl_ciphersuites = xstrdup(arg);
> }
>
> +/* Sets the server name override for SNI (Server Name Indication).
> + * If 'server_name' is NULL, clears any existing override and SNI will
> + * use the hostname from the connection string. */
> +void
> +stream_ssl_set_server_name(const char *server_name)
> +{
> + free(ssl_server_name_override);
> + ssl_server_name_override = nullable_xstrdup(server_name);
> +}
> +
> /* Set SSL/TLS protocols based on the string input. Aborts with an error
> * message if 'arg' is invalid. */
> void
> diff --git a/lib/stream-ssl.h b/lib/stream-ssl.h
> index abd3ba219..6127d6fb1 100644
> --- a/lib/stream-ssl.h
> +++ b/lib/stream-ssl.h
> @@ -28,11 +28,13 @@ void stream_ssl_set_key_and_cert(const char
> *private_key_file,
> void stream_ssl_set_protocols(const char *arg);
> void stream_ssl_set_ciphers(const char *arg);
> void stream_ssl_set_ciphersuites(const char *arg);
> +void stream_ssl_set_server_name(const char *server_name);
>
> #define SSL_OPTION_ENUMS \
> OPT_SSL_PROTOCOLS, \
> OPT_SSL_CIPHERS, \
> - OPT_SSL_CIPHERSUITES
> + OPT_SSL_CIPHERSUITES, \
> + OPT_SSL_SERVER_NAME
>
> #define STREAM_SSL_LONG_OPTIONS \
> {"private-key", required_argument, NULL, 'p'}, \
> @@ -40,7 +42,8 @@ void stream_ssl_set_ciphersuites(const char *arg);
> {"ca-cert", required_argument, NULL, 'C'}, \
> {"ssl-protocols", required_argument, NULL, OPT_SSL_PROTOCOLS}, \
> {"ssl-ciphers", required_argument, NULL, OPT_SSL_CIPHERS}, \
> - {"ssl-ciphersuites", required_argument, NULL, OPT_SSL_CIPHERSUITES}
> + {"ssl-ciphersuites", required_argument, NULL, OPT_SSL_CIPHERSUITES},
> \
> + {"ssl-server-name", required_argument, NULL, OPT_SSL_SERVER_NAME}
>
> #define STREAM_SSL_OPTION_HANDLERS \
> case 'p': \
> @@ -65,10 +68,14 @@ void stream_ssl_set_ciphersuites(const char *arg);
> \
> case OPT_SSL_CIPHERSUITES: \
> stream_ssl_set_ciphersuites(optarg); \
> + break; \
> + \
> + case OPT_SSL_SERVER_NAME: \
> + stream_ssl_set_server_name(optarg); \
> break;
>
> #define STREAM_SSL_CASES \
> case 'p': case 'c': case 'C': case OPT_SSL_PROTOCOLS: \
> - case OPT_SSL_CIPHERS: case OPT_SSL_CIPHERSUITES:
> + case OPT_SSL_CIPHERS: case OPT_SSL_CIPHERSUITES: case
> OPT_SSL_SERVER_NAME:
>
> #endif /* stream-ssl.h */
> diff --git a/lib/stream.c b/lib/stream.c
> index aa48a973b..feaa1cb2d 100644
> --- a/lib/stream.c
> +++ b/lib/stream.c
> @@ -163,7 +163,9 @@ stream_usage(const char *name, bool active, bool passive,
> " --ssl-ciphers=CIPHERS list of SSL/TLS ciphers to enable\n"
> " with TLSv1.2\n"
> " --ssl-ciphersuites=SUITES list of SSL/TLS ciphersuites to\n"
> - " enable with TLSv1.3 and later\n");
> + " enable with TLSv1.3 and later\n"
> + " --ssl-server-name=NAME server name for TLS Server Name\n"
> + " Indication (SNI)\n");
> #endif
> }
>
> diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
> index 5303a46c6..86b0dc7b8 100644
> --- a/ovsdb/ovsdb-server.c
> +++ b/ovsdb/ovsdb-server.c
> @@ -74,6 +74,7 @@ static char *ca_cert_file;
> static char *ssl_protocols;
> static char *ssl_ciphers;
> static char *ssl_ciphersuites;
> +static char *ssl_server_name;
> static bool bootstrap_ca_cert;
>
> /* Try to reclaim heap memory back to system after DB compaction. */
> @@ -1793,6 +1794,7 @@ reconfigure_ssl(const struct shash *all_dbs)
> const char *resolved_ssl_protocols;
> const char *resolved_ssl_ciphers;
> const char *resolved_ssl_ciphersuites;
> + const char *resolved_ssl_server_name;
>
> resolved_private_key = query_db_string(all_dbs, private_key_file,
> &errors);
> resolved_certificate = query_db_string(all_dbs, certificate_file,
> &errors);
> @@ -1801,12 +1803,15 @@ reconfigure_ssl(const struct shash *all_dbs)
> resolved_ssl_ciphers = query_db_string(all_dbs, ssl_ciphers, &errors);
> resolved_ssl_ciphersuites = query_db_string(all_dbs, ssl_ciphersuites,
> &errors);
> + resolved_ssl_server_name = query_db_string(all_dbs, ssl_server_name,
> + &errors);
>
> stream_ssl_set_key_and_cert(resolved_private_key, resolved_certificate);
> stream_ssl_set_ca_cert_file(resolved_ca_cert, bootstrap_ca_cert);
> stream_ssl_set_protocols(resolved_ssl_protocols);
> stream_ssl_set_ciphers(resolved_ssl_ciphers);
> stream_ssl_set_ciphersuites(resolved_ssl_ciphersuites);
> + stream_ssl_set_server_name(resolved_ssl_server_name);
>
> return errors.string;
> }
> @@ -2707,6 +2712,10 @@ parse_options(int argc, char *argv[],
> ssl_ciphersuites = optarg;
> break;
>
> + case OPT_SSL_SERVER_NAME:
> + ssl_server_name = optarg;
> + break;
> +
> case OPT_BOOTSTRAP_CA_CERT:
> ca_cert_file = optarg;
> bootstrap_ca_cert = true;
> diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
> index 59245fff8..9b9d73c5f 100644
> --- a/tests/ovs-vsctl.at
> +++ b/tests/ovs-vsctl.at
> @@ -1769,6 +1769,62 @@ AT_CHECK([grep "server name" ovsdb-server.log], [0],
> OVS_VSCTL_CLEANUP
> AT_CLEANUP
>
> +AT_SETUP([TLS server name indication (SNI) with --ssl-server-name])
> +AT_KEYWORDS([ovs-vsctl ssl tls sni client])
> +AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
> +OVSDB_INIT([conf.db])
> +PKIDIR=$abs_top_builddir/tests
> +
> +# This test validates the --ssl-server-name option for SNI.
> +# Test 1: Connect to IP with --ssl-server-name to verify SNI override.
> +# Test 2: Connect to same IP without --ssl-server-name (no SNI sent).
> +
> +AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile \
> + --private-key=$PKIDIR/testpki-privkey.pem \
> + --certificate=$PKIDIR/testpki-cert.pem \
> + --ca-cert=$PKIDIR/testpki-cacert.pem \
> + --remote=pssl:0:127.0.0.1 \
> + -vstream_ssl:file:dbg conf.db], [0], [ignore], [ignore])
> +PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
> +
> +# Test 1: SNI override - connect to IP but specify server name.
> +# This validates that --ssl-server-name overrides connection hostname.
> +AT_CHECK([ovs-vsctl -t 5 --no-wait \
> + --db=ssl:127.0.0.1:$SSL_PORT \
> + --private-key=$PKIDIR/testpki-privkey.pem \
> + --certificate=$PKIDIR/testpki-cert.pem \
> + --ca-cert=$PKIDIR/testpki-cacert.pem \
> + --ssl-server-name=sni-test.example \
> + add-br br0])
> +
> +# Verify SNI was sent with the overridden name.
> +OVS_WAIT_UNTIL([grep -q \
> + "connection indicated server name sni-test.example" \
> + ovsdb-server.log])
> +
> +# Save current log size for Test 2.
> +LOG_SIZE=$(wc -l < ovsdb-server.log)
> +
> +# Test 2: Default behavior without SNI override - should NOT show SNI
> +# connecting to IP address (no hostname to extract).
> +AT_CHECK([ovs-vsctl -t 5 --no-wait \
> + --db=ssl:127.0.0.1:$SSL_PORT \
> + --private-key=$PKIDIR/testpki-privkey.pem \
> + --certificate=$PKIDIR/testpki-cert.pem \
> + --ca-cert=$PKIDIR/testpki-cacert.pem \
> + add-br br1])
> +
> +# Stop server to ensure logs are flushed before checking.
> +OVS_VSCTL_CLEANUP
> +
> +# Check that no new SNI messages appeared in Test 2 (connecting to IP
> +# without --ssl-server-name should not generate SNI).
> +AT_CHECK([tail -n +$(($LOG_SIZE + 1)) ovsdb-server.log | \
> + grep -q "connection indicated server name"], [1])
> +
> +AT_CAPTURE_FILE([ovsdb-server.log])
nit: This should be close to the daemon start at the top. This macro says
"print out this file on failure". Placing it at the end doesn't make a
lot of sense, as we'll never get to it on failure. We're also missing the
on_exit hook to kill ovsdb-server process on failure.
Above two issues are pretty minor, so I fixed them myself. Also added a
small remark to the NEWS record to avoid it sounding like we didn't support
SNI at all before.
With that, applied to main. Thanks!
Best regards, Ilya Maximets.
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev