You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by sh...@apache.org on 2023/03/31 17:32:48 UTC
[trafficserver] 01/01: Add allow-plain server ports attribute
This is an automated email from the ASF dual-hosted git repository.
shinrich pushed a commit to branch allow-plain
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 13cac6ef4cebe05806a8dfda7a52f3096a41c761
Author: Susan Hinrichs <sh...@ieee.org>
AuthorDate: Fri Mar 31 17:32:21 2023 +0000
Add allow-plain server ports attribute
---
include/records/I_RecHttp.h | 3 ++
iocore/net/P_SSLNetVConnection.h | 15 ++++++
iocore/net/P_SSLNextProtocolAccept.h | 3 +-
iocore/net/SSLNetVConnection.cc | 90 +++++++++++++++++++++++++++++---
iocore/net/SSLNextProtocolAccept.cc | 49 ++++++++++-------
proxy/ProtocolProbeSessionAccept.cc | 10 +++-
proxy/http/HttpProxyServerMain.cc | 2 +-
src/records/RecHttp.cc | 7 +++
tests/gold_tests/tls/allow-plain.test.py | 80 ++++++++++++++++++++++++++++
9 files changed, 229 insertions(+), 30 deletions(-)
diff --git a/include/records/I_RecHttp.h b/include/records/I_RecHttp.h
index 9285828b6..817b5988f 100644
--- a/include/records/I_RecHttp.h
+++ b/include/records/I_RecHttp.h
@@ -271,6 +271,8 @@ public:
bool m_outbound_transparent_p = false;
// True if transparent pass-through is enabled on this port.
bool m_transparent_passthrough = false;
+ // True if allow-plain is enabled on this port.
+ bool m_allow_plain = false;
/// True if MPTCP is enabled on this port.
bool m_mptcp = false;
/// Local address for inbound connections (listen address).
@@ -420,6 +422,7 @@ public:
static const char *const OPT_TRANSPARENT_OUTBOUND; ///< Outbound transparent.
static const char *const OPT_TRANSPARENT_FULL; ///< Full transparency.
static const char *const OPT_TRANSPARENT_PASSTHROUGH; ///< Pass-through non-HTTP.
+ static const char *const OPT_ALLOW_PLAIN; ///< Backup to plain HTTP.
static const char *const OPT_SSL; ///< SSL (experimental)
static const char *const OPT_QUIC; ///< QUIC (experimental)
static const char *const OPT_PROXY_PROTO; ///< Proxy Protocol
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index 6fcacdf72..c1392c990 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -174,6 +174,18 @@ public:
transparentPassThrough = val;
}
+ bool
+ getAllowPlain() const
+ {
+ return allowPlain;
+ }
+
+ void
+ setAllowPlain(bool val)
+ {
+ allowPlain = val;
+ }
+
// Copy up here so we overload but don't override
using super::reenable;
@@ -432,6 +444,7 @@ private:
int handShakeBioStored = 0;
bool transparentPassThrough = false;
+ bool allowPlain = false;
int sent_cert = 0;
@@ -476,6 +489,8 @@ private:
void _make_ssl_connection(SSL_CTX *ctx);
void _bindSSLObject();
void _unbindSSLObject();
+ UnixNetVConnection *_migrateFromSSL();
+ void _propagateHandShakeBuffer(UnixNetVConnection *target, EThread *t);
int _ssl_read_from_net(EThread *lthread, int64_t &ret);
ssl_error_t _ssl_read_buffer(void *buf, int64_t nbytes, int64_t &nread);
diff --git a/iocore/net/P_SSLNextProtocolAccept.h b/iocore/net/P_SSLNextProtocolAccept.h
index 18070cd6b..512960441 100644
--- a/iocore/net/P_SSLNextProtocolAccept.h
+++ b/iocore/net/P_SSLNextProtocolAccept.h
@@ -33,7 +33,7 @@
class SSLNextProtocolAccept : public SessionAccept
{
public:
- SSLNextProtocolAccept(Continuation *, bool);
+ SSLNextProtocolAccept(Continuation *, bool, bool);
~SSLNextProtocolAccept() override;
bool accept(NetVConnection *, MIOBuffer *, IOBufferReader *) override;
@@ -60,6 +60,7 @@ private:
SSLNextProtocolSet protoset;
SessionProtocolSet protoenabled;
bool transparent_passthrough;
+ bool allow_plain;
friend struct SSLNextProtocolTrampoline;
};
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 97c94082e..12e1567a8 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -1332,13 +1332,23 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
err = errno;
SSLVCDebug(this, "SSL handshake error: %s (%d), errno=%d", SSLErrorName(ssl_error), ssl_error, err);
- // start a blind tunnel if tr-pass is set and data does not look like ClientHello
char *buf = handShakeBuffer ? handShakeBuffer->buf() : nullptr;
- if (getTransparentPassThrough() && buf && *buf != SSL_OP_HANDSHAKE) {
- SSLVCDebug(this, "Data does not look like SSL handshake, starting blind tunnel");
- this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
- sslHandshakeStatus = SSL_HANDSHAKE_ONGOING;
- return EVENT_CONT;
+ if (buf && *buf != SSL_OP_HANDSHAKE) {
+ if (getAllowPlain()) {
+ SSLVCDebug(this, "Try plain");
+ // If this doesn't look like a ClientHello, convert this connection to a UnixNetVC and send the
+ // packet for Http Processing
+ this->_migrateFromSSL();
+ return EVENT_CONT;
+ } else if (getTransparentPassThrough()) {
+ // start a blind tunnel if tr-pass is set and data does not look like ClientHello
+ SSLVCDebug(this, "Data does not look like SSL handshake, starting blind tunnel");
+ this->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
+ sslHandshakeStatus = SSL_HANDSHAKE_ONGOING;
+ return EVENT_CONT;
+ } else {
+ SSLVCDebug(this, "Give up");
+ }
}
}
@@ -2106,6 +2116,74 @@ SSLNetVConnection::_getNetProcessor()
return &sslNetProcessor;
}
+void
+SSLNetVConnection::_propagateHandShakeBuffer(UnixNetVConnection *target, EThread *t)
+{
+ Debug("ssl", "allow-plain, handshake buffer ready to read=%" PRId64, this->handShakeHolder->read_avail());
+ // Take ownership of the handShake buffer
+ this->sslHandshakeStatus = SSL_HANDSHAKE_DONE;
+ NetState *s = &target->read;
+ s->vio.buffer.writer_for(this->handShakeBuffer);
+ s->vio.set_reader(this->handShakeHolder);
+ s->vio.vc_server = target;
+ s->vio.cont = this->read.vio.cont;
+ s->vio.mutex = this->read.vio.cont->mutex;
+ this->handShakeReader = nullptr;
+ this->handShakeHolder = nullptr;
+ this->handShakeBuffer = nullptr;
+
+ // Kick things again, so the data that was copied into the
+ // vio.read buffer gets processed
+ target->readSignalDone(VC_EVENT_READ_COMPLETE, get_NetHandler(t));
+}
+
+/*
+ * Replaces the current SSLNetVConnection with a UnixNetVConnection
+ * Propagates any data in the SSL handShakeBuffer to be processed
+ * by the UnixNetVConnection logic
+ */
+UnixNetVConnection *
+SSLNetVConnection::_migrateFromSSL()
+{
+ EThread *t = this_ethread();
+ NetHandler *client_nh = get_NetHandler(t);
+ ink_assert(client_nh);
+
+ Connection hold_con;
+ hold_con.move(this->con);
+
+ // We will leave the SSL object with the original SSLNetVC to be
+ // cleaned up. Only moving the socket and handShakeBuffer
+ // So no need to call _prepareMigration
+
+ // Do_io_close will signal the VC to be freed on the original thread
+ // Since we moved the con context, the fd will not be closed
+ // Go ahead and remove the fd from the original thread's epoll structure, so it is not
+ // processed on two threads simultaneously
+ this->ep.stop();
+
+ // Create new VC:
+ UnixNetVConnection *newvc = static_cast<UnixNetVConnection *>(unix_netProcessor.allocate_vc(t));
+ ink_assert(newvc != nullptr);
+ if (newvc != nullptr && newvc->populate(hold_con, this->read.vio.cont, nullptr) != EVENT_DONE) {
+ newvc->do_io_close();
+ Debug("ssl", "Failed to populate unixvc for allow-plain");
+ newvc = nullptr;
+ }
+ if (newvc != nullptr) {
+ newvc->attributes = HttpProxyPort::TRANSPORT_DEFAULT;
+ newvc->set_is_transparent(this->is_transparent);
+ newvc->set_context(get_context());
+ newvc->options = this->options;
+ Debug("ssl", "Move to unixvc for allow-plain");
+ this->_propagateHandShakeBuffer(newvc, t);
+ }
+
+ // Do not mark this closed until the end so it does not get freed by the other thread too soon
+ this->do_io_close();
+ return newvc;
+}
+
ssl_curve_id
SSLNetVConnection::_get_tls_curve() const
{
diff --git a/iocore/net/SSLNextProtocolAccept.cc b/iocore/net/SSLNextProtocolAccept.cc
index fcec55908..b7da75169 100644
--- a/iocore/net/SSLNextProtocolAccept.cc
+++ b/iocore/net/SSLNextProtocolAccept.cc
@@ -93,25 +93,32 @@ struct SSLNextProtocolTrampoline : public Continuation {
return EVENT_ERROR;
}
- // Cancel the action, so later timeouts and errors don't try to
- // send the event to the Accept object. After this point, the accept
- // object does not care.
- netvc->set_action(nullptr);
-
- Continuation *endpoint_cont = netvc->endpoint();
- if (!endpoint_cont) {
- // Route to the default endpoint
- endpoint_cont = npnParent->endpoint;
- }
-
- if (endpoint_cont) {
- // disable read io, send events to endpoint
- netvc->do_io_read(endpoint_cont, 0, nullptr);
-
- send_plugin_event(endpoint_cont, NET_EVENT_ACCEPT, netvc);
+ // This wasn't really a TLS connection
+ // Trying to process it as a TCP connection
+ if (netvc == nullptr) {
+ UnixNetVConnection *plain_netvc = dynamic_cast<UnixNetVConnection *>(vio->vc_server);
+ send_plugin_event(npnParent->endpoint, NET_EVENT_ACCEPT, plain_netvc);
} else {
- // No handler, what should we do? Best to just kill the VC while we can.
- netvc->do_io_close();
+ // Cancel the action, so later timeouts and errors don't try to
+ // send the event to the Accept object. After this point, the accept
+ // object does not care.
+ netvc->set_action(nullptr);
+
+ Continuation *endpoint_cont = netvc->endpoint();
+ if (!endpoint_cont) {
+ // Route to the default endpoint
+ endpoint_cont = npnParent->endpoint;
+ }
+
+ if (endpoint_cont) {
+ // disable read io, send events to endpoint
+ netvc->do_io_read(endpoint_cont, 0, nullptr);
+
+ send_plugin_event(endpoint_cont, NET_EVENT_ACCEPT, netvc);
+ } else {
+ // No handler, what should we do? Best to just kill the VC while we can.
+ netvc->do_io_close();
+ }
}
delete this;
@@ -132,6 +139,7 @@ SSLNextProtocolAccept::mainEvent(int event, void *edata)
ink_release_assert(netvc != nullptr);
netvc->setTransparentPassThrough(transparent_passthrough);
+ netvc->setAllowPlain(allow_plain);
// Register our protocol set with the VC and kick off a zero-length read to
// force the SSLNetVConnection to complete the SSL handshake. Don't tell
@@ -167,11 +175,12 @@ SSLNextProtocolAccept::enableProtocols(const SessionProtocolSet &protos)
this->protoenabled = protos;
}
-SSLNextProtocolAccept::SSLNextProtocolAccept(Continuation *ep, bool transparent_passthrough)
+SSLNextProtocolAccept::SSLNextProtocolAccept(Continuation *ep, bool transparent_passthrough, bool allow_plain)
: SessionAccept(nullptr),
buffer(new_empty_MIOBuffer(SSLConfigParams::ssl_misc_max_iobuffer_size_index)),
endpoint(ep),
- transparent_passthrough(transparent_passthrough)
+ transparent_passthrough(transparent_passthrough),
+ allow_plain(allow_plain)
{
SET_HANDLER(&SSLNextProtocolAccept::mainEvent);
}
diff --git a/proxy/ProtocolProbeSessionAccept.cc b/proxy/ProtocolProbeSessionAccept.cc
index f9351b62f..327ae7e26 100644
--- a/proxy/ProtocolProbeSessionAccept.cc
+++ b/proxy/ProtocolProbeSessionAccept.cc
@@ -170,8 +170,14 @@ ProtocolProbeSessionAccept::mainEvent(int event, void *data)
ink_assert(data);
VIO *vio;
- NetVConnection *netvc = static_cast<NetVConnection *>(data);
- ProtocolProbeTrampoline *probe = new ProtocolProbeTrampoline(this, netvc->mutex, nullptr, nullptr);
+ NetVConnection *netvc = static_cast<NetVConnection *>(data);
+ ProtocolProbeTrampoline *probe;
+ UnixNetVConnection *unix_netvc = dynamic_cast<UnixNetVConnection *>(netvc);
+ if (unix_netvc != nullptr && unix_netvc->read.vio.get_writer() != nullptr) {
+ probe = new ProtocolProbeTrampoline(this, netvc->mutex, unix_netvc->read.vio.get_writer(), unix_netvc->read.vio.get_reader());
+ } else {
+ probe = new ProtocolProbeTrampoline(this, netvc->mutex, nullptr, nullptr);
+ }
// The connection has completed, set the accept inactivity timeout here to watch over the difference between the
// connection set up and the first transaction..
diff --git a/proxy/http/HttpProxyServerMain.cc b/proxy/http/HttpProxyServerMain.cc
index aff54f9c1..5f38807b5 100644
--- a/proxy/http/HttpProxyServerMain.cc
+++ b/proxy/http/HttpProxyServerMain.cc
@@ -220,7 +220,7 @@ MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned
ProtocolSessionCreateMap.insert({TS_ALPN_PROTOCOL_INDEX_HTTP_2_0, create_h2_server_session});
if (port.isSSL()) {
- SSLNextProtocolAccept *ssl = new SSLNextProtocolAccept(probe, port.m_transparent_passthrough);
+ SSLNextProtocolAccept *ssl = new SSLNextProtocolAccept(probe, port.m_transparent_passthrough, port.m_allow_plain);
// ALPN selects the first server-offered protocol,
// so make sure that we offer the newest protocol first.
diff --git a/src/records/RecHttp.cc b/src/records/RecHttp.cc
index 48490ae1a..b6f29b826 100644
--- a/src/records/RecHttp.cc
+++ b/src/records/RecHttp.cc
@@ -183,6 +183,7 @@ const char *const HttpProxyPort::OPT_TRANSPARENT_INBOUND = "tr-in";
const char *const HttpProxyPort::OPT_TRANSPARENT_OUTBOUND = "tr-out";
const char *const HttpProxyPort::OPT_TRANSPARENT_FULL = "tr-full";
const char *const HttpProxyPort::OPT_TRANSPARENT_PASSTHROUGH = "tr-pass";
+const char *const HttpProxyPort::OPT_ALLOW_PLAIN = "allow-plain";
const char *const HttpProxyPort::OPT_SSL = "ssl";
const char *const HttpProxyPort::OPT_PROXY_PROTO = "pp";
const char *const HttpProxyPort::OPT_PLUGIN = "plugin";
@@ -442,6 +443,8 @@ HttpProxyPort::processOptions(const char *opts)
#else
Warning("Transparent pass-through requested [%s] in port descriptor '%s' but TPROXY was not configured.", item, opts);
#endif
+ } else if (0 == strcasecmp(OPT_ALLOW_PLAIN, item)) {
+ m_allow_plain = true;
} else if (0 == strcasecmp(OPT_MPTCP, item)) {
if (mptcp_supported()) {
m_mptcp = true;
@@ -647,6 +650,10 @@ HttpProxyPort::print(char *out, size_t n)
zret += snprintf(out + zret, n - zret, ":%s", OPT_TRANSPARENT_PASSTHROUGH);
}
+ if (m_allow_plain) {
+ zret += snprintf(out + zret, n - zret, ":%s", OPT_ALLOW_PLAIN);
+ }
+
/* Don't print the IP resolution preferences if the port is outbound
* transparent (which means the preference order is forced) or if
* the order is the same as the default.
diff --git a/tests/gold_tests/tls/allow-plain.test.py b/tests/gold_tests/tls/allow-plain.test.py
new file mode 100644
index 000000000..2f5ba33f1
--- /dev/null
+++ b/tests/gold_tests/tls/allow-plain.test.py
@@ -0,0 +1,80 @@
+'''
+Test the allow-plain attribute of the ssl port
+Clients sending non-tls request to ssl port should get passed to
+non-tls processing in ATS if the allow-plain attibute is present
+'''
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+import os
+
+Test.Summary = '''
+Test allow-plain attributed
+'''
+
+Test.ContinueOnFail = True
+
+# Define default ATS
+ts = Test.MakeATSProcess("ts", enable_tls=True)
+server = Test.MakeOriginServer("server")
+
+testName = "VIA"
+
+# We only need one transaction as only the VIA header will be checked.
+request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+server.addResponse("sessionlog.json", request_header, response_header)
+
+ts.addDefaultSSLFiles()
+
+ts.Disk.records_config.update({
+ 'proxy.config.http.server_ports': '{0}:ssl:allow-plain'.format(ts.Variables.ssl_port),
+ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.diags.debug.enabled': 0,
+ 'proxy.config.diags.debug.tags': 'ssl|http',
+})
+
+ts.Disk.remap_config.AddLine(
+ 'map / http://127.0.0.1:{0}'.format(server.Variables.Port)
+)
+
+ts.Disk.ssl_multicert_config.AddLine(
+ 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+# TLS curl should work of course
+tr = Test.AddTestRun()
+# Wait for the micro server
+tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
+# Delay on readiness of our ssl ports
+tr.Processes.Default.StartBefore(Test.Processes.ts)
+
+tr.Processes.Default.Command = 'curl -k --verbose --ipv4 --http1.1 --resolve www.example.com:{}:127.0.0.1 https://www.example.com:{}'.format(
+ ts.Variables.ssl_port, ts.Variables.ssl_port)
+tr.Processes.Default.ReturnCode = 0
+tr.StillRunningAfter = server
+tr.StillRunningAfter = ts
+tr.Processes.Default.Streams.all = Testers.ContainsExpression("TLS", "Should negiotiate TLS")
+
+# non-TLS curl should also work to the same port
+tr2 = Test.AddTestRun()
+tr2.Processes.Default.Command = 'curl --verbose --ipv4 --http1.1 --resolve www.example.com:{}:127.0.0.1 http://www.example.com:{}'.format(
+ ts.Variables.ssl_port, ts.Variables.ssl_port)
+tr2.Processes.Default.ReturnCode = 0
+tr2.StillRunningAfter = server
+tr2.StillRunningAfter = ts
+tr2.Processes.Default.Streams.all = Testers.ExcludesExpression("TLS", "Should not negiotiate TLS")