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:47 UTC

[trafficserver] branch allow-plain created (now 13cac6ef4)

This is an automated email from the ASF dual-hosted git repository.

shinrich pushed a change to branch allow-plain
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


      at 13cac6ef4 Add allow-plain server ports attribute

This branch includes the following new commits:

     new 13cac6ef4 Add allow-plain server ports attribute

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[trafficserver] 01/01: Add allow-plain server ports attribute

Posted by sh...@apache.org.
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")