-
Notifications
You must be signed in to change notification settings - Fork 851
Add support for more PP fields #12864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
autest filter_body failed |
|
[approve ci autest 0] |
|
Needs #12863 to make the new fields work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds support for PROXY Protocol v2 SSL TLV subtypes (cipher and TLS version) and exposes them as new access log fields, enabling operators to log the upstream TLS details provided by a load balancer.
Changes:
- Add
ProxyProtocol::get_tlv_ssl_cipher()/get_tlv_ssl_version()to extract SSL sub-TLV values. - Add new LogAccess marshalers and register new log fields
pptc/pptv. - Extend Proxy Protocol parser unit tests and update admin logging field documentation.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/iocore/net/ProxyProtocol.cc | Implements SSL sub-TLV parsing helpers and getters for TLS cipher/version. |
| include/iocore/net/ProxyProtocol.h | Declares new public getters and a private helper for SSL sub-TLV parsing. |
| src/proxy/logging/LogAccess.cc | Adds marshalers for the new Proxy Protocol TLS cipher/version log fields. |
| include/proxy/logging/LogAccess.h | Declares new LogAccess marshal methods for the new fields. |
| src/proxy/logging/Log.cc | Registers two new logging fields (pptc, pptv) mapped to the new marshalers. |
| src/iocore/net/unit_tests/test_ProxyProtocol.cc | Extends TLV parsing test coverage to include SSL cipher/version subtypes. |
| doc/admin-guide/logging/formatting.en.rst | Documents the new log field symbols. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
include/iocore/net/ProxyProtocol.h
Outdated
| private: | ||
| std::string additional_data; | ||
|
|
||
| std::optional<std::string_view> _get_tlv_ssl_subtype(int subtype) const; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ProxyProtocol::_get_tlv_ssl_subtype takes int subtype, but subtype IDs are 8-bit values on the wire (and get_tlv() uses uint8_t). Using uint8_t here avoids accidental sign/width issues and better communicates the expected range.
| std::optional<std::string_view> _get_tlv_ssl_subtype(int subtype) const; | |
| std::optional<std::string_view> _get_tlv_ssl_subtype(uint8_t subtype) const; |
| // Find the given subtype | ||
| uint16_t len = ssl.length(); | ||
| const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) | ||
| const char *end = p + len + 1; | ||
| while (p != end) { |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_get_tlv_ssl_subtype() computes the end pointer incorrectly (it uses end = p + len + 1 after already advancing p by 5). This makes the loop read past the end of the PP2_TYPE_SSL TLV value and can cause out-of-bounds reads/undefined behavior. Compute end from the start of the SSL value (e.g., ssl.data() + ssl.size()) and iterate while p < end (not p != end).
| // Find the given subtype | |
| uint16_t len = ssl.length(); | |
| const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) | |
| const char *end = p + len + 1; | |
| while (p != end) { | |
| // The SSL TLV must contain at least the client (uint8_t) and verify (uint32_t) fields. | |
| if (ssl.length() < 5) { | |
| Dbg(dbg_ctl_proxyprotocol_v2, "SSL TLV too short: %zu bytes (expected at least 5)", static_cast<size_t>(ssl.length())); | |
| return std::nullopt; | |
| } | |
| // Find the given subtype | |
| uint16_t len = ssl.length(); | |
| const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) | |
| const char *end = ssl.data() + len; // End of the SSL TLV value | |
| while (p < end) { |
| if (auto v = tlv.find(PP2_TYPE_SSL); v != tlv.end() && v->second.length() != 0) { | ||
| auto ssl = v->second; | ||
|
|
||
| // Is the client connected over TLS | ||
| if ((ssl.data()[0] & 0x01) == 0) { | ||
| // Not over TLS | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| // Find the given subtype | ||
| uint16_t len = ssl.length(); | ||
| const char *p = ssl.data() + 5; // Skip client (uint8_t) + verify (uint32_t) |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_get_tlv_ssl_subtype() reads ssl.data()[0] and skips 5 bytes without verifying the TLV value is at least 5 bytes long. If PP2_TYPE_SSL is present but truncated/malformed, this will access beyond the string_view. Add a ssl.size() >= 5 (and preferably >= 5 + 3 before parsing sub-TLVs) guard before dereferencing and parsing.
| len = padded_length(cipher->size() + 1); | ||
| if (buf) { | ||
| marshal_str(buf, cipher->data(), len); | ||
| buf[cipher->size()] = '\0'; | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LogAccess::marshal_proxy_protocol_tls_cipher() passes a non-NUL-terminated std::string_view buffer (cipher->data()) into marshal_str(), which uses ink_strlcpy and will read past the end of the view looking for a terminator. Use marshal_mem() with the actual length (cipher->size()) and the padded length, or otherwise ensure the input is NUL-terminated before calling marshal_str().
| int len = INK_MIN_ALIGN; | ||
|
|
||
| if (m_http_sm) { | ||
| if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); cipher) { | ||
| len = padded_length(cipher->size() + 1); | ||
| if (buf) { | ||
| marshal_str(buf, cipher->data(), len); | ||
| buf[cipher->size()] = '\0'; | ||
| } | ||
| } | ||
| } | ||
| return len; | ||
| } | ||
|
|
||
| int | ||
| LogAccess::marshal_proxy_protocol_tls_version(char *buf) | ||
| { | ||
| int len = INK_MIN_ALIGN; | ||
|
|
||
| if (m_http_sm) { | ||
| if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); version) { | ||
| len = padded_length(version->size() + 1); | ||
| if (buf) { | ||
| marshal_str(buf, version->data(), len); | ||
| buf[version->size()] = '\0'; | ||
| } | ||
| } | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
marshal_proxy_protocol_tls_cipher() returns INK_MIN_ALIGN when no cipher is available but does not write anything into buf in that case. This can leave uninitialized bytes in the log buffer. Consider always marshalling a default string (e.g., "-") when buf is non-null, and return the padded length that matches what was written.
| int len = INK_MIN_ALIGN; | |
| if (m_http_sm) { | |
| if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); cipher) { | |
| len = padded_length(cipher->size() + 1); | |
| if (buf) { | |
| marshal_str(buf, cipher->data(), len); | |
| buf[cipher->size()] = '\0'; | |
| } | |
| } | |
| } | |
| return len; | |
| } | |
| int | |
| LogAccess::marshal_proxy_protocol_tls_version(char *buf) | |
| { | |
| int len = INK_MIN_ALIGN; | |
| if (m_http_sm) { | |
| if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); version) { | |
| len = padded_length(version->size() + 1); | |
| if (buf) { | |
| marshal_str(buf, version->data(), len); | |
| buf[version->size()] = '\0'; | |
| } | |
| } | |
| } | |
| int len = INK_MIN_ALIGN; | |
| bool have_data = false; | |
| if (m_http_sm) { | |
| if (auto cipher = m_http_sm->t_state.pp_info.get_tlv_ssl_cipher(); cipher) { | |
| have_data = true; | |
| len = padded_length(cipher->size() + 1); | |
| if (buf) { | |
| marshal_str(buf, cipher->data(), len); | |
| buf[cipher->size()] = '\0'; | |
| } | |
| } | |
| } | |
| // When no cipher is available, ensure the buffer is initialized with a default value. | |
| if (buf && !have_data) { | |
| buf[0] = '-'; | |
| // NUL-terminate and pad the remaining bytes in the field. | |
| for (int i = 1; i < len; ++i) { | |
| buf[i] = '\0'; | |
| } | |
| } | |
| return len; | |
| } | |
| int | |
| LogAccess::marshal_proxy_protocol_tls_version(char *buf) | |
| { | |
| int len = INK_MIN_ALIGN; | |
| bool have_data = false; | |
| if (m_http_sm) { | |
| if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); version) { | |
| have_data = true; | |
| len = padded_length(version->size() + 1); | |
| if (buf) { | |
| marshal_str(buf, version->data(), len); | |
| buf[version->size()] = '\0'; | |
| } | |
| } | |
| } | |
| // When no TLS version is available, ensure the buffer is initialized with a default value. | |
| if (buf && !have_data) { | |
| buf[0] = '-'; | |
| // NUL-terminate and pad the remaining bytes in the field. | |
| for (int i = 1; i < len; ++i) { | |
| buf[i] = '\0'; | |
| } | |
| } |
| len = padded_length(version->size() + 1); | ||
| if (buf) { | ||
| marshal_str(buf, version->data(), len); | ||
| buf[version->size()] = '\0'; | ||
| } |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LogAccess::marshal_proxy_protocol_tls_version() has the same issue as the cipher field: version->data() is a std::string_view that is not guaranteed NUL-terminated, but marshal_str() will read until it finds a terminator. Use marshal_mem() with the view length and padded length (or ensure NUL termination) to avoid out-of-bounds reads.
| int len = INK_MIN_ALIGN; | ||
|
|
||
| if (m_http_sm) { | ||
| if (auto version = m_http_sm->t_state.pp_info.get_tlv_ssl_version(); version) { | ||
| len = padded_length(version->size() + 1); | ||
| if (buf) { | ||
| marshal_str(buf, version->data(), len); | ||
| buf[version->size()] = '\0'; | ||
| } | ||
| } | ||
| } | ||
| return len; |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
marshal_proxy_protocol_tls_version() returns INK_MIN_ALIGN when the TLS version TLV isn't present, but does not marshal any default value into buf in that case. This can result in uninitialized log output for the field. Align the behavior with other string marshalers by always writing a default string when buf is provided and returning the corresponding padded length.
| Authority to the |TS| | ||
| pptv Proxy Protocol The TLS version from Proxy Protocol context from the LB | ||
| Authority to the |TS| |
Copilot
AI
Feb 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new log field table entries for pptc/pptv look like copy/paste from ppa: the second column label is "Authority" for both, which doesn't match the TLS cipher/version descriptions. Update the field name in the middle column to something like "TLS Cipher" and "TLS Version" so the table is internally consistent.
| Authority to the |TS| | |
| pptv Proxy Protocol The TLS version from Proxy Protocol context from the LB | |
| Authority to the |TS| | |
| TLS Cipher to the |TS| | |
| pptv Proxy Protocol The TLS version from Proxy Protocol context from the LB | |
| TLS Version to the |TS| |
0b343d0 to
2aa259f
Compare
This adds support for
PP2_SUBTYPE_SSL_CIPHERandPP2_SUBTYPE_SSL_VERSION, and log fields for those.