trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zw...@apache.org
Subject [trafficserver] branch 9.1.x updated: Add ALPN support on TLS Partial Blind Tunnel (#7511)
Date Tue, 16 Mar 2021 20:55:30 GMT
This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/9.1.x by this push:
     new f277e19  Add ALPN support on TLS Partial Blind Tunnel (#7511)
f277e19 is described below

commit f277e190e566301797354d4527429d5a81679021
Author: Masaori Koshiba <masaori@apache.org>
AuthorDate: Wed Feb 24 07:59:04 2021 +0900

    Add ALPN support on TLS Partial Blind Tunnel (#7511)
    
    (cherry picked from commit e6291181b4eb6bc98b57fa1cd86ec07fd2d59928)
---
 doc/admin-guide/files/sni.yaml.en.rst |  5 +++++
 iocore/net/P_SNIActionPerformer.h     | 10 +++++++++-
 iocore/net/SSLNetVConnection.cc       | 23 ++++++++++++++++++++++-
 iocore/net/SSLSNIConfig.cc            |  2 +-
 iocore/net/YamlSNIConfig.cc           | 32 +++++++++++++++++++++++++++++++-
 iocore/net/YamlSNIConfig.h            |  2 ++
 lib/records/I_RecHttp.h               | 11 +++++++++++
 lib/records/RecHttp.cc                | 31 +++++++++++++++++++++++++++++++
 proxy/http/HttpSM.cc                  |  8 ++++++++
 9 files changed, 120 insertions(+), 4 deletions(-)

diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst
index bc8be27..1000f1c 100644
--- a/doc/admin-guide/files/sni.yaml.en.rst
+++ b/doc/admin-guide/files/sni.yaml.en.rst
@@ -139,6 +139,11 @@ partial_blind_route       Destination as an FQDN and port, separated
by a colon
                           In addition partial_blind_route creates a new TLS connection to
the specified origin.
                           It does not interpret the decrypted data before passing it to the
origin TLS
                           connection, so the contents do not need to be HTTP.
+
+tunnel_alpn               List of ALPN Protocol Ids for Partial Blind Tunnel.
+
+                          ATS negotiates application protocol with the client on behalf of
the origin server.
+                          This only works with ``partial_blind_route``.
 ========================= ========================================================================================
 
 Client verification, via ``verify_client``, corresponds to setting
diff --git a/iocore/net/P_SNIActionPerformer.h b/iocore/net/P_SNIActionPerformer.h
index 07d6c5c..636a07a 100644
--- a/iocore/net/P_SNIActionPerformer.h
+++ b/iocore/net/P_SNIActionPerformer.h
@@ -95,7 +95,8 @@ private:
 class TunnelDestination : public ActionItem
 {
 public:
-  TunnelDestination(const std::string_view &dest, SNIRoutingType type) : destination(dest),
type(type)
+  TunnelDestination(const std::string_view &dest, SNIRoutingType type, const std::vector<int>
&alpn)
+    : destination(dest), type(type), alpn_ids(alpn)
   {
     need_fix = (destination.find_first_of('$') != std::string::npos);
   }
@@ -115,9 +116,15 @@ public:
       } else {
         ssl_netvc->set_tunnel_destination(destination, type);
       }
+
       if (type == SNIRoutingType::BLIND) {
         ssl_netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL;
       }
+
+      // ALPN
+      for (int id : alpn_ids) {
+        ssl_netvc->enableProtocol(id);
+      }
     }
 
     return SSL_TLSEXT_ERR_OK;
@@ -191,6 +198,7 @@ private:
 
   std::string destination;
   SNIRoutingType type = SNIRoutingType::NONE;
+  const std::vector<int> &alpn_ids;
   bool need_fix;
 };
 
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 99688bb..f7ac772 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -1133,6 +1133,7 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
 
       SSL_set_verify(this->ssl, SSL_VERIFY_PEER, verify_callback);
 
+      // SNI
       ats_scoped_str &tlsext_host_name = this->options.sni_hostname ? this->options.sni_hostname
: this->options.sni_servername;
       if (tlsext_host_name) {
         if (SSL_set_tlsext_host_name(this->ssl, tlsext_host_name)) {
@@ -1142,6 +1143,16 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
           SSL_INCREMENT_DYN_STAT(ssl_sni_name_set_failure);
         }
       }
+
+      // ALPN
+      if (!this->options.alpn_protos.empty()) {
+        if (int res = SSL_set_alpn_protos(this->ssl, reinterpret_cast<const uint8_t
*>(this->options.alpn_protos.data()),
+                                          this->options.alpn_protos.size());
+            res != 0) {
+          Debug("ssl.error", "failed to set ALPN '%.*s' for client handshake", static_cast<int>(this->options.alpn_protos.size()),
+                this->options.alpn_protos.data());
+        }
+      }
     }
 
     return sslClientHandShakeEvent(err);
@@ -1305,6 +1316,15 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
       SSL_INCREMENT_DYN_STAT_EX(ssl_total_handshake_time_stat, ssl_handshake_time);
       SSL_INCREMENT_DYN_STAT(ssl_total_success_handshake_count_in_stat);
     }
+
+    if (_tunnel_type != SNIRoutingType::NONE) {
+      // Foce to use HTTP/1.1 endpoint for SNI Routing
+      if (!this->setSelectedProtocol(reinterpret_cast<const unsigned char *>(IP_PROTO_TAG_HTTP_1_1.data()),
+                                     IP_PROTO_TAG_HTTP_1_1.size())) {
+        return EVENT_ERROR;
+      }
+    }
+
     {
       const unsigned char *proto = nullptr;
       unsigned len               = 0;
@@ -1321,7 +1341,7 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
       }
 
       if (len) {
-        if (!this->setSelectedProtocol(proto, len)) {
+        if (_tunnel_type == SNIRoutingType::NONE && !this->setSelectedProtocol(proto,
len)) {
           return EVENT_ERROR;
         }
         this->set_negotiated_protocol_id({reinterpret_cast<const char *>(proto),
static_cast<size_t>(len)});
@@ -1331,6 +1351,7 @@ SSLNetVConnection::sslServerHandShakeEvent(int &err)
         Debug("ssl", "client did not select a next protocol");
       }
     }
+
 #if TS_USE_TLS_ASYNC
     if (SSLConfigParams::async_handshake_enabled) {
       SSL_clear_mode(ssl, SSL_MODE_ASYNC);
diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc
index 2f4379d..647b687 100644
--- a/iocore/net/SSLSNIConfig.cc
+++ b/iocore/net/SSLSNIConfig.cc
@@ -77,7 +77,7 @@ SNIConfigParams::loadSNIConfig()
       ai->actions.push_back(std::make_unique<TLSValidProtocols>(item.protocol_mask));
     }
     if (item.tunnel_destination.length() > 0) {
-      ai->actions.push_back(std::make_unique<TunnelDestination>(item.tunnel_destination,
item.tunnel_type));
+      ai->actions.push_back(std::make_unique<TunnelDestination>(item.tunnel_destination,
item.tunnel_type, item.tunnel_alpn));
     }
 
     ai->actions.push_back(std::make_unique<SNI_IpAllow>(item.ip_allow, item.fqdn));
diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc
index aa1d86b..348a7d7 100644
--- a/iocore/net/YamlSNIConfig.cc
+++ b/iocore/net/YamlSNIConfig.cc
@@ -28,12 +28,37 @@
 #include <yaml-cpp/yaml.h>
 #include <openssl/ssl.h>
 
+#include "P_SNIActionPerformer.h"
+
 #include "tscore/Diags.h"
 #include "tscore/EnumDescriptor.h"
 #include "tscore/Errata.h"
 #include "tscore/ink_assert.h"
-#include "P_SNIActionPerformer.h"
+
 #include "records/I_RecCore.h"
+#include "records/I_RecHttp.h"
+
+namespace
+{
+// Assuming node is value of [TS_tunnel_alpn]
+void
+load_tunnel_alpn(std::vector<int> &dst, const YAML::Node &node)
+{
+  if (!node.IsSequence()) {
+    throw YAML::ParserException(node.Mark(), "\"tunnel_alpn\" is not sequence");
+  }
+
+  for (const auto &alpn : node) {
+    auto value = alpn.as<std::string>();
+    int index  = globalSessionProtocolNameRegistry.indexFor(value);
+    if (index == SessionProtocolNameRegistry::INVALID) {
+      throw YAML::ParserException(alpn.Mark(), "unknown value \"" + value + "\"");
+    } else {
+      dst.push_back(index);
+    }
+  }
+}
+} // namespace
 
 ts::Errata
 YamlSNIConfig::loader(const char *cfgFilename)
@@ -104,6 +129,7 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn,
                                                TS_tunnel_route,
                                                TS_forward_route,
                                                TS_partial_blind_route,
+                                               TS_tunnel_alpn,
                                                TS_verify_server_policy,
                                                TS_verify_server_properties,
                                                TS_client_cert,
@@ -214,6 +240,10 @@ template <> struct convert<YamlSNIConfig::Item> {
     } else if (node[TS_partial_blind_route]) {
       item.tunnel_destination = node[TS_partial_blind_route].as<std::string>();
       item.tunnel_type        = SNIRoutingType::PARTIAL_BLIND;
+
+      if (node[TS_tunnel_alpn]) {
+        load_tunnel_alpn(item.tunnel_alpn, node[TS_tunnel_alpn]);
+      }
     }
 
     if (node[TS_verify_server_policy]) {
diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h
index 1116953..6b58116 100644
--- a/iocore/net/YamlSNIConfig.h
+++ b/iocore/net/YamlSNIConfig.h
@@ -38,6 +38,7 @@ TSDECL(verify_client_ca_certs);
 TSDECL(tunnel_route);
 TSDECL(forward_route);
 TSDECL(partial_blind_route);
+TSDECL(tunnel_alpn);
 TSDECL(verify_server_policy);
 TSDECL(verify_server_properties);
 TSDECL(verify_origin_server);
@@ -88,6 +89,7 @@ struct YamlSNIConfig {
     std::string ip_allow;
     bool protocol_unset = true;
     unsigned long protocol_mask;
+    std::vector<int> tunnel_alpn{};
 
     void EnableProtocol(YamlSNIConfig::TLSProtocol proto);
   };
diff --git a/lib/records/I_RecHttp.h b/lib/records/I_RecHttp.h
index 0349ed1..36f8714 100644
--- a/lib/records/I_RecHttp.h
+++ b/lib/records/I_RecHttp.h
@@ -167,6 +167,8 @@ public:
   static int constexpr MAX     = SessionProtocolSet::MAX; ///< Maximum # of registered
names.
   static int constexpr INVALID = -1;                      ///< Normalized invalid index
value.
 
+  static std::string_view convert_openssl_alpn_wire_format(int index);
+
   using TextView = ts::TextView;
 
   /// Default constructor.
@@ -195,6 +197,15 @@ public:
   */
   TextView nameFor(int index) const;
 
+  /** Convert an @a index to the corresponding name in OpenSSL ALPN wire format.
+
+      OpenSSL ALPN wire format (vector of non-empty, 8-bit length-prefixed, byte strings)
+      https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_alpn_protos.html
+
+      @return A pointer to the name or @c nullptr if the index isn't registered.
+  */
+  static TextView wireNameFor(int index);
+
   /// Mark protocols as present in @a sp_set based on the names in @a value.
   /// The names can be separated by ;/|,: and space.
   /// @internal This is separated out to make it easy to access from the plugin API
diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc
index 00afaab..7e9a62d 100644
--- a/lib/records/RecHttp.cc
+++ b/lib/records/RecHttp.cc
@@ -198,6 +198,12 @@ size_t const OPT_OUTBOUND_IP_PREFIX_LEN = strlen(HttpProxyPort::OPT_OUTBOUND_IP_
 size_t const OPT_INBOUND_IP_PREFIX_LEN  = strlen(HttpProxyPort::OPT_INBOUND_IP_PREFIX);
 size_t const OPT_HOST_RES_PREFIX_LEN    = strlen(HttpProxyPort::OPT_HOST_RES_PREFIX);
 size_t const OPT_PROTO_PREFIX_LEN       = strlen(HttpProxyPort::OPT_PROTO_PREFIX);
+
+constexpr std::string_view TS_ALPN_PROTO_ID_OPENSSL_HTTP_0_9("\x8http/0.9");
+constexpr std::string_view TS_ALPN_PROTO_ID_OPENSSL_HTTP_1_0("\x8http/1.0");
+constexpr std::string_view TS_ALPN_PROTO_ID_OPENSSL_HTTP_1_1("\x8http/1.1");
+constexpr std::string_view TS_ALPN_PROTO_ID_OPENSSL_HTTP_2("\x2h2");
+constexpr std::string_view TS_ALPN_PROTO_ID_OPENSSL_HTTP_3("\x2h3");
 } // namespace
 
 namespace
@@ -760,6 +766,31 @@ RecNormalizeProtoTag(const char *tag)
   return findResult == TSProtoTags.end() ? nullptr : findResult->data();
 }
 
+/**
+   Convert TS_ALPN_PROTOCOL_INDEX_* into OpenSSL ALPN Wire Format
+
+   https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_alpn_protos.html
+
+   TODO: support dynamic generation of wire format
+ */
+std::string_view
+SessionProtocolNameRegistry::convert_openssl_alpn_wire_format(int index)
+{
+  if (index == TS_ALPN_PROTOCOL_INDEX_HTTP_0_9) {
+    return TS_ALPN_PROTO_ID_OPENSSL_HTTP_0_9;
+  } else if (index == TS_ALPN_PROTOCOL_INDEX_HTTP_1_0) {
+    return TS_ALPN_PROTO_ID_OPENSSL_HTTP_1_0;
+  } else if (index == TS_ALPN_PROTOCOL_INDEX_HTTP_1_1) {
+    return TS_ALPN_PROTO_ID_OPENSSL_HTTP_1_1;
+  } else if (index == TS_ALPN_PROTOCOL_INDEX_HTTP_2_0) {
+    return TS_ALPN_PROTO_ID_OPENSSL_HTTP_2;
+  } else if (index == TS_ALPN_PROTOCOL_INDEX_HTTP_3) {
+    return TS_ALPN_PROTO_ID_OPENSSL_HTTP_3;
+  }
+
+  return {};
+}
+
 int
 SessionProtocolNameRegistry::toIndex(ts::TextView name)
 {
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index ee1b09a..f6a276f 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -5192,6 +5192,14 @@ HttpSM::do_http_server_open(bool raw)
     SSLNetVConnection *ssl_vc = dynamic_cast<SSLNetVConnection *>(ua_txn->get_netvc());
     if (ssl_vc && raw) {
       tls_upstream = ssl_vc->upstream_tls();
+
+      // ALPN on TLS Partial Blind Tunnel - set negotiated ALPN id
+      if (ssl_vc->tunnel_type() == SNIRoutingType::PARTIAL_BLIND) {
+        int pid = ssl_vc->get_negotiated_protocol_id();
+        if (pid != SessionProtocolNameRegistry::INVALID) {
+          opt.alpn_protos = SessionProtocolNameRegistry::convert_openssl_alpn_wire_format(pid);
+        }
+      }
     }
     opt.local_port = ua_txn->get_outbound_port();
 


Mime
View raw message