diff options
Diffstat (limited to 'src/zenserver/proxy/httptrafficinspector.cpp')
| -rw-r--r-- | src/zenserver/proxy/httptrafficinspector.cpp | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/src/zenserver/proxy/httptrafficinspector.cpp b/src/zenserver/proxy/httptrafficinspector.cpp new file mode 100644 index 000000000..74ecbfd48 --- /dev/null +++ b/src/zenserver/proxy/httptrafficinspector.cpp @@ -0,0 +1,197 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "httptrafficinspector.h" + +#include <zencore/logging.h> +#include <zencore/string.h> + +#include <charconv> + +namespace zen { + +// clang-format off +http_parser_settings HttpTrafficInspector::s_RequestSettings{ + .on_message_begin = [](http_parser*) { return 0; }, + .on_url = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnUrl(Data, Len); }, + .on_status = [](http_parser*, const char*, size_t) { return 0; }, + .on_header_field = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }, + .on_header_value = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }, + .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, + .on_body = [](http_parser*, const char*, size_t) { return 0; }, + .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, + .on_chunk_header{}, + .on_chunk_complete{}}; + +http_parser_settings HttpTrafficInspector::s_ResponseSettings{ + .on_message_begin = [](http_parser*) { return 0; }, + .on_url = [](http_parser*, const char*, size_t) { return 0; }, + .on_status = [](http_parser*, const char*, size_t) { return 0; }, + .on_header_field = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderField(Data, Len); }, + .on_header_value = [](http_parser* p, const char* Data, size_t Len) { return GetThis(p)->OnHeaderValue(Data, Len); }, + .on_headers_complete = [](http_parser* p) { return GetThis(p)->OnHeadersComplete(); }, + .on_body = [](http_parser*, const char*, size_t) { return 0; }, + .on_message_complete = [](http_parser* p) { return GetThis(p)->OnMessageComplete(); }, + .on_chunk_header{}, + .on_chunk_complete{}}; +// clang-format on + +HttpTrafficInspector::HttpTrafficInspector(Direction Dir, std::string_view SessionLabel) +: m_Log(logging::Get("proxy.http")) +, m_Direction(Dir) +, m_SessionLabel(SessionLabel) +{ + http_parser_init(&m_Parser, Dir == Direction::Request ? HTTP_REQUEST : HTTP_RESPONSE); + m_Parser.data = this; +} + +void +HttpTrafficInspector::Inspect(const char* Data, size_t Length) +{ + if (m_Disabled) + { + return; + } + + http_parser_settings* Settings = (m_Direction == Direction::Request) ? &s_RequestSettings : &s_ResponseSettings; + + size_t Parsed = http_parser_execute(&m_Parser, Settings, Data, Length); + + if (m_Parser.upgrade) + { + if (m_Direction == Direction::Request) + { + ZEN_DEBUG("[{}] >> {} {} (upgrade to WebSocket)", m_SessionLabel, m_Method, m_Url); + } + else + { + ZEN_DEBUG("[{}] << {} (upgrade to WebSocket)", m_SessionLabel, m_StatusCode); + } + ResetMessageState(); + m_Upgraded.store(true, std::memory_order_relaxed); + m_Disabled = true; + return; + } + + http_errno Error = HTTP_PARSER_ERRNO(&m_Parser); + if (Error != HPE_OK) + { + ZEN_DEBUG("[{}] non-HTTP traffic detected ({}), disabling inspection", m_SessionLabel, http_errno_name(Error)); + m_Disabled = true; + } + else if (Parsed != Length) + { + ZEN_DEBUG("[{}] parser consumed {}/{} bytes, disabling inspection", m_SessionLabel, Parsed, Length); + m_Disabled = true; + } +} + +int +HttpTrafficInspector::OnUrl(const char* Data, size_t Length) +{ + m_Url.append(Data, Length); + return 0; +} + +int +HttpTrafficInspector::OnHeaderField(const char* Data, size_t Length) +{ + m_CurrentHeaderField.assign(Data, Length); + return 0; +} + +int +HttpTrafficInspector::OnHeaderValue(const char* Data, size_t Length) +{ + if (m_CurrentHeaderField.size() == 14 && StrCaseCompare(m_CurrentHeaderField.c_str(), "Content-Length", 14) == 0) + { + int64_t Value = 0; + std::from_chars(Data, Data + Length, Value); + m_ContentLength = Value; + } + else if (!m_SessionIdCaptured && m_CurrentHeaderField.size() == 10 && + StrCaseCompare(m_CurrentHeaderField.c_str(), "UE-Session", 10) == 0) + { + Oid Parsed; + if (Oid::TryParse(std::string_view(Data, Length), Parsed)) + { + m_SessionId = Parsed; + m_SessionIdCaptured = true; + } + } + m_CurrentHeaderField.clear(); + return 0; +} + +int +HttpTrafficInspector::OnHeadersComplete() +{ + if (m_Direction == Direction::Request) + { + m_Method = http_method_str(static_cast<http_method>(m_Parser.method)); + } + else + { + m_StatusCode = m_Parser.status_code; + } + return 0; +} + +int +HttpTrafficInspector::OnMessageComplete() +{ + if (m_Direction == Direction::Request) + { + if (m_ContentLength >= 0) + { + ZEN_DEBUG("[{}] >> {} {} (content-length: {})", m_SessionLabel, m_Method, m_Url, m_ContentLength); + } + else + { + ZEN_DEBUG("[{}] >> {} {}", m_SessionLabel, m_Method, m_Url); + } + } + else + { + if (m_ContentLength >= 0) + { + ZEN_DEBUG("[{}] << {} (content-length: {})", m_SessionLabel, m_StatusCode, m_ContentLength); + } + else + { + ZEN_DEBUG("[{}] << {}", m_SessionLabel, m_StatusCode); + } + } + + if (m_Observer) + { + m_Observer->OnMessageComplete(m_Direction, m_Method, m_Url, m_StatusCode, m_ContentLength); + } + + m_MessageCount.fetch_add(1, std::memory_order_relaxed); + ResetMessageState(); + return 0; +} + +Oid +HttpTrafficInspector::GetSessionId() const +{ + return m_SessionId; +} + +bool +HttpTrafficInspector::HasSessionId() const +{ + return m_SessionIdCaptured; +} + +void +HttpTrafficInspector::ResetMessageState() +{ + m_Url.clear(); + m_Method.clear(); + m_StatusCode = 0; + m_ContentLength = -1; + m_CurrentHeaderField.clear(); +} + +} // namespace zen |