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")