Add new log field to output server name sent by client in TLS handshake.
diff --git a/doc/admin-guide/logging/formatting.en.rst b/doc/admin-guide/logging/formatting.en.rst
index fd6a396..182199c 100644
--- a/doc/admin-guide/logging/formatting.en.rst
+++ b/doc/admin-guide/logging/formatting.en.rst
@@ -579,6 +579,7 @@
SSL / Encryption
~~~~~~~~~~~~~~~~
+.. _cssn:
.. _cqssl:
.. _cqssr:
.. _cqssv:
@@ -592,6 +593,10 @@
===== ============== ==========================================================
Field Source Description
===== ============== ==========================================================
+cssn Client TLS SNI server name in client Hello message in TLS handshake.
+ Hello If no server name present in Hello, or the transaction
+ was not over TLS (over TCP), this field will contain
+ ``-``.
cqssl Client Request SSL client request status indicates if this client
connection is over SSL.
cqssr Client Request SSL session ticket reused status; indicates if the current
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index 60ce374..83ff6d6 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -36,6 +36,8 @@
#include "tscore/ink_platform.h"
#include "ts/apidefs.h"
#include <string_view>
+#include <cstring>
+#include <memory>
#include <openssl/ssl.h>
#include <openssl/err.h>
@@ -480,6 +482,7 @@
bool tunnel_decrypt = false;
X509_STORE_CTX *verify_cert = nullptr;
+ // Null-terminated string, or nullptr if there is no SNI server name.
std::unique_ptr<char[]> _serverName;
};
diff --git a/proxy/Makefile.am b/proxy/Makefile.am
index bac2ea4..33a2ad1 100644
--- a/proxy/Makefile.am
+++ b/proxy/Makefile.am
@@ -18,6 +18,8 @@
include $(top_srcdir)/build/tidy.mk
+include private/Makefile.inc
+
SUBDIRS = hdrs shared http http2 logging
if ENABLE_QUIC
SUBDIRS += http3
@@ -44,6 +46,7 @@
Show.h
libproxy_a_SOURCES = \
+ $(PRIVATE_SOURCES_) \
CacheControl.cc \
CacheControl.h \
ControlBase.cc \
diff --git a/proxy/ProxySession.cc b/proxy/ProxySession.cc
index d3b7eb4..0298866 100644
--- a/proxy/ProxySession.cc
+++ b/proxy/ProxySession.cc
@@ -24,6 +24,7 @@
#include "HttpConfig.h"
#include "HttpDebugNames.h"
#include "ProxySession.h"
+#include "P_SSLNetVConnection.h"
ProxySession::ProxySession() : VConnection(nullptr)
{
@@ -79,6 +80,7 @@
this->api_hooks.clear();
this->mutex.clear();
this->acl.clear();
+ this->_ssl.reset();
}
int
@@ -244,9 +246,20 @@
NetVConnection *netvc = get_netvc();
return netvc ? netvc->get_remote_addr() : nullptr;
}
+
sockaddr const *
ProxySession::get_local_addr()
{
NetVConnection *netvc = get_netvc();
return netvc ? netvc->get_local_addr() : nullptr;
}
+
+void
+ProxySession::_handle_if_ssl(NetVConnection *new_vc)
+{
+ auto ssl_vc = dynamic_cast<SSLNetVConnection *>(new_vc);
+ if (ssl_vc) {
+ _ssl = std::make_unique<SSLProxySession>();
+ _ssl.get()->init(*ssl_vc);
+ }
+}
diff --git a/proxy/ProxySession.h b/proxy/ProxySession.h
index 478c592..e70b683 100644
--- a/proxy/ProxySession.h
+++ b/proxy/ProxySession.h
@@ -27,11 +27,13 @@
#include "tscore/ink_resolver.h"
#include "tscore/TSSystemState.h"
#include <string_view>
+#include <memory>
#include "P_Net.h"
#include "InkAPIInternal.h"
#include "http/Http1ServerSession.h"
#include "http/HttpSessionAccept.h"
#include "IPAllow.h"
+#include "private/SSLProxySession.h"
// Emit a debug message conditional on whether this particular client session
// has debugging enabled. This should only be called from within a client session
@@ -143,6 +145,10 @@
APIHook *hook_get(TSHttpHookID id) const;
HttpAPIHooks const *feature_hooks() const;
+
+ // Returns null pointer if session does not use a TLS connection.
+ SSLProxySession const *ssl() const;
+
////////////////////
// Members
@@ -167,6 +173,10 @@
int64_t con_id = 0;
Event *schedule_event = nullptr;
+ // This function should be called in all overrides of new_connection() where
+ // the new_vc may be an SSLNetVConnection object.
+ void _handle_if_ssl(NetVConnection *new_vc);
+
private:
void handle_api_return(int event);
int state_api_callout(int event, void *edata);
@@ -180,6 +190,8 @@
// be active until the transaction goes through or the client
// aborts.
bool m_active = false;
+
+ std::unique_ptr<SSLProxySession> _ssl;
};
///////////////////
@@ -267,3 +279,9 @@
{
return this->api_hooks.has_hooks() || http_global_hooks->has_hooks();
}
+
+inline SSLProxySession const *
+ProxySession::ssl() const
+{
+ return _ssl.get();
+}
diff --git a/proxy/http/Http1ClientSession.cc b/proxy/http/Http1ClientSession.cc
index 38705df..1196b3d 100644
--- a/proxy/http/Http1ClientSession.cc
+++ b/proxy/http/Http1ClientSession.cc
@@ -195,6 +195,8 @@
_reader = reader ? reader : read_buffer->alloc_reader();
trans.set_reader(_reader);
+ _handle_if_ssl(new_vc);
+
// INKqa11186: Use a local pointer to the mutex as
// when we return from do_api_callout, the ClientSession may
// have already been deallocated.
diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc
index d703925..e2fdcdb 100644
--- a/proxy/http2/Http2ClientSession.cc
+++ b/proxy/http2/Http2ClientSession.cc
@@ -216,6 +216,8 @@
this->write_buffer = new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX);
this->sm_writer = this->write_buffer->alloc_reader();
+ this->_handle_if_ssl(new_vc);
+
do_api_callout(TS_HTTP_SSN_START_HOOK);
}
diff --git a/proxy/logging/Log.cc b/proxy/logging/Log.cc
index 3584cb0..9f39ea9 100644
--- a/proxy/logging/Log.cc
+++ b/proxy/logging/Log.cc
@@ -462,6 +462,11 @@
global_field_list.add(field, false);
field_symbol_hash.emplace("cluc", field);
+ field = new LogField("client_sni_server_name", "cssn", LogField::STRING, &LogAccess::marshal_client_sni_server_name,
+ reinterpret_cast<LogField::UnmarshalFunc>(&LogAccess::unmarshal_str));
+ global_field_list.add(field, false);
+ field_symbol_hash.emplace("cssn", field);
+
field = new LogField("process_uuid", "puuid", LogField::STRING, &LogAccess::marshal_process_uuid,
reinterpret_cast<LogField::UnmarshalFunc>(&LogAccess::unmarshal_str));
global_field_list.add(field, false);
diff --git a/proxy/logging/LogAccess.cc b/proxy/logging/LogAccess.cc
index 22c964a..6e3aac4 100644
--- a/proxy/logging/LogAccess.cc
+++ b/proxy/logging/LogAccess.cc
@@ -1266,6 +1266,38 @@
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
+int
+LogAccess::marshal_client_sni_server_name(char *buf)
+{
+ // NOTE: For this string_view, data() must always be nul-terminated, but the nul character must not be included in
+ // the length.
+ //
+ std::string_view server_name = "";
+
+ if (m_http_sm) {
+ auto txn = m_http_sm->get_ua_txn();
+ if (txn) {
+ auto ssn = txn->get_proxy_ssn();
+ if (ssn) {
+ auto ssl = ssn->ssl();
+ if (ssl) {
+ auto server_name_str = ssl->client_sni_server_name();
+ if (server_name_str) {
+ server_name = server_name_str;
+ }
+ }
+ }
+ }
+ }
+ int len = round_strlen(server_name.length() + 1);
+ if (buf) {
+ marshal_str(buf, server_name.data(), len);
+ }
+ return len;
+}
+
+/*-------------------------------------------------------------------------
+ -------------------------------------------------------------------------*/
int
LogAccess::marshal_client_host_port(char *buf)
diff --git a/proxy/logging/LogAccess.h b/proxy/logging/LogAccess.h
index 0cfb333..fa83bc8 100644
--- a/proxy/logging/LogAccess.h
+++ b/proxy/logging/LogAccess.h
@@ -250,6 +250,7 @@
inkcoreapi int marshal_client_http_transaction_priority_weight(char *); // INT
inkcoreapi int marshal_client_http_transaction_priority_dependence(char *); // INT
inkcoreapi int marshal_cache_lookup_url_canon(char *); // STR
+ inkcoreapi int marshal_client_sni_server_name(char *); // STR
// named fields from within a http header
//
diff --git a/proxy/private/Makefile.inc b/proxy/private/Makefile.inc
new file mode 100644
index 0000000..1a4a127
--- /dev/null
+++ b/proxy/private/Makefile.inc
@@ -0,0 +1,22 @@
+# 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.
+
+# Header files in this directory should only be included by files in the
+# parent directory.
+
+PRIVATE_SOURCES_ = \
+ private/SSLProxySession.cc \
+ private/SSLProxySession.h
diff --git a/proxy/private/SSLProxySession.cc b/proxy/private/SSLProxySession.cc
new file mode 100644
index 0000000..544d0af
--- /dev/null
+++ b/proxy/private/SSLProxySession.cc
@@ -0,0 +1,39 @@
+/** @file
+
+ Implementation file for SSLProxySession class.
+
+ @section license License
+
+ 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.
+ */
+
+#include <cstring>
+
+#include "SSLProxySession.h"
+#include "P_Net.h"
+
+void
+SSLProxySession::init(SSLNetVConnection const &new_vc)
+{
+ char const *name = new_vc.get_server_name();
+ int length = std::strlen(name) + 1;
+ if (length > 1) {
+ char *n = new char[length];
+ std::memcpy(n, name, length);
+ _client_sni_server_name.reset(n);
+ }
+}
diff --git a/proxy/private/SSLProxySession.h b/proxy/private/SSLProxySession.h
new file mode 100644
index 0000000..37e572f
--- /dev/null
+++ b/proxy/private/SSLProxySession.h
@@ -0,0 +1,46 @@
+/** @file
+
+ Header file for SSLProxySession class.
+
+ @section license License
+
+ 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <string_view>
+
+class SSLNetVConnection;
+
+class SSLProxySession
+{
+public:
+ // Returns null pointer if no SNI server name, otherwise pointer to null-terminated string.
+ //
+ char const *
+ client_sni_server_name() const
+ {
+ return _client_sni_server_name.get();
+ }
+
+ void init(SSLNetVConnection const &new_vc);
+
+private:
+ std::unique_ptr<char[]> _client_sni_server_name;
+};
diff --git a/tests/gold_tests/logging/ccid_ctid.test.py b/tests/gold_tests/logging/new_log_flds.test.py
similarity index 77%
rename from tests/gold_tests/logging/ccid_ctid.test.py
rename to tests/gold_tests/logging/new_log_flds.test.py
index 34f9d75..fb787a5 100644
--- a/tests/gold_tests/logging/ccid_ctid.test.py
+++ b/tests/gold_tests/logging/new_log_flds.test.py
@@ -19,7 +19,7 @@
import os
Test.Summary = '''
-Test new ccid and ctid log fields
+Test new log fields
'''
# need Curl
Test.SkipUnless(
@@ -47,6 +47,10 @@
'map https://127.0.0.1:{0} https://httpbin.org/ip'.format(ts.Variables.ssl_port)
)
+ts.Disk.remap_config.AddLine(
+ 'map https://reallyreallyreallyreallylong.com https://httpbin.org/ip'.format(ts.Variables.ssl_port)
+)
+
ts.Disk.ssl_multicert_config.AddLine(
'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
)
@@ -56,9 +60,9 @@
logging:
formats:
- name: custom
- format: "%<ccid> %<ctid>"
+ format: "%<ccid> %<ctid> %<cssn>"
logs:
- - filename: test_ccid_ctid
+ - filename: test_new_log_flds
format: custom
'''.split("\n")
)
@@ -82,8 +86,17 @@
tr.Processes.Default.ReturnCode = 0
tr = Test.AddTestRun()
-tr.Processes.Default.Command = 'curl "https://127.0.0.1:{0}" "https://127.0.0.1:{0}" --http2 --insecure --verbose'.format(
- ts.Variables.ssl_port)
+tr.Processes.Default.Command = (
+ 'curl "https://127.0.0.1:{0}" "https://127.0.0.1:{0}" --http2 --insecure --verbose'.format(
+ ts.Variables.ssl_port)
+)
+tr.Processes.Default.ReturnCode = 0
+
+tr = Test.AddTestRun()
+tr.Processes.Default.Command = (
+ 'curl "https://reallyreallyreallyreallylong.com:{0}" --http2 --insecure --verbose' +
+ ' --resolve reallyreallyreallyreallylong.com:{0}:127.0.0.1'
+).format(ts.Variables.ssl_port)
tr.Processes.Default.ReturnCode = 0
# Delay to allow TS to flush report to disk, then validate generated log.
@@ -91,6 +104,6 @@
tr = Test.AddTestRun()
tr.DelayStart = 10
tr.Processes.Default.Command = 'python {0} < {1}'.format(
- os.path.join(Test.TestDirectory, 'ccid_ctid_observer.py'),
- os.path.join(ts.Variables.LOGDIR, 'test_ccid_ctid.log'))
+ os.path.join(Test.TestDirectory, 'new_log_flds_observer.py'),
+ os.path.join(ts.Variables.LOGDIR, 'test_new_log_flds.log'))
tr.Processes.Default.ReturnCode = 0
diff --git a/tests/gold_tests/logging/ccid_ctid_observer.py b/tests/gold_tests/logging/new_log_flds_observer.py
similarity index 76%
rename from tests/gold_tests/logging/ccid_ctid_observer.py
rename to tests/gold_tests/logging/new_log_flds_observer.py
index 1b4cee5..0fba8c1 100644
--- a/tests/gold_tests/logging/ccid_ctid_observer.py
+++ b/tests/gold_tests/logging/new_log_flds_observer.py
@@ -1,5 +1,5 @@
'''
-Examines log generated by ccid_ctid.test.py, returns 0 if valid, 1 if not.
+Examines log generated by new_log_flds.test.py, returns 0 if valid, 1 if not.
'''
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -23,10 +23,12 @@
ccid = []
ctid = []
-# Read in ccid and ctid fields from each line of the generated report.
+# Read in log fields from each line of the generated report.
#
+ln_num = 0
for ln in csv.reader(sys.stdin, delimiter=' '):
- if len(ln) != 2:
+ ln_num += 1
+ if len(ln) != 3:
exit(code=1)
i = int(ln[0])
if i < 0:
@@ -36,6 +38,12 @@
if i < 0:
exit(code=1)
ctid.append(i)
+ if ln_num == 7:
+ if ln[2] != "reallyreallyreallyreallylong.com":
+ exit(code=1)
+ else:
+ if ln[2] != "-":
+ exit(code=1)
# Validate contents of report.
#
@@ -45,7 +53,8 @@
ctid[2] != ctid[3] and
ccid[3] != ccid[4] and
ccid[4] == ccid[5] and
- ctid[4] != ctid[5]):
+ ctid[4] != ctid[5] and
+ ccid[5] != ccid[6]):
exit(code=0)
# Failure exit if report was not valid.