-
Notifications
You must be signed in to change notification settings - Fork 18
feat: Implement TLV listener filter support #83
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
feat: Implement TLV listener filter support #83
Conversation
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.
Summary of Changes
Hello @Eeshu-Yadav, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a new listener filter designed to process TLV (Type-Length-Value) encoded data. This feature is crucial for extracting original destination information from network connections, particularly for use cases like Kmesh waypoint connections. The changes involve defining the filter's configuration via protobuf, implementing the core logic for parsing TLV streams, and integrating it into the existing listener framework to enable dynamic application based on listener settings.
Highlights
- TLV Listener Filter Definition: Added a new protobuf definition for the TLV listener filter, allowing configuration of a maximum TLV length.
- TlvListenerFilter Implementation: Implemented the
TlvListenerFilterstruct, which includes asynchronous stream processing capabilities for both IPv4 and IPv6 traffic. - TLV Type Support: The filter now supports
TLV_TYPE_SERVICE_ADDRESS(0x1) for extracting original destination addresses andTLV_TYPE_ENDING(0xfe) to signify the end of TLV data. - Asynchronous Stream Processing: The filter incorporates asynchronous stream processing with rewind capability to efficiently handle incoming data.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
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.
Code Review
This pull request introduces a TLV listener filter to extract original destination information from connections. The implementation is comprehensive, covering protobuf definitions, configuration, and the filter logic itself. However, I've identified a few critical issues that need to be addressed. These include using an incorrect enum variant for metadata, a bug in the destination extraction logic that will fail with multiple TLVs, an incorrect protobuf type URL, and ignoring the filter's configuration. I've also included a suggestion for a minor performance improvement in the stream processing loop. Addressing these points will ensure the filter is robust and correctly configurable.
| "type.googleapis.com/envoy.extensions.filters.listener.tlv_listener_filter.v3.TlvListenerFilter" => { | ||
| let _config = crate::orion::extensions::filters::listener::tlv_listener_filter::v3::TlvListenerFilter::decode(typed_config.value.as_slice()) | ||
| .map_err(|e| GenericError::from_msg_with_cause("failed to decode TlvListenerFilter protobuf", e))?; | ||
| Ok(Self::TlvListenerFilter) | ||
| }, |
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.
There are a couple of issues in this block:
- Critical: The type URL for the TLV listener filter is incorrect. The protobuf package is
orion.extensions.filters.listener.tlv_listener_filter.v3, so the type URL should betype.googleapis.com/orion.extensions.filters.listener.tlv_listener_filter.v3.TlvListenerFilter, not one prefixed withenvoy.. This will cause deserialization of the filter from anAnyprotobuf to fail. - High: The
TlvListenerFilterconfiguration is decoded but then its value (_config) is discarded. Themax_tlv_lengthfield from the configuration is ignored, and a default value will be used in the filter implementation. The configuration should be stored and passed to the filter.
This will require some refactoring to pass the configuration down to orion-lib.
"type.googleapis.com/orion.extensions.filters.listener.tlv_listener_filter.v3.TlvListenerFilter" => {
let _config = crate::orion::extensions::filters::listener::tlv_listener_filter::v3::TlvListenerFilter::decode(typed_config.value.as_slice())
.map_err(|e| GenericError::from_msg_with_cause("failed to decode TlvListenerFilter protobuf", e))?;
// TODO: The `_config` should be passed down to the filter implementation.
Ok(Self::TlvListenerFilter)
},| let metadata = DownstreamConnectionMetadata::FromProxyProtocol { | ||
| original_peer_address: peer_address, | ||
| original_destination_address: original_dest, | ||
| protocol: v2::Protocol::Stream, | ||
| tlv_data: std::collections::HashMap::new(), | ||
| proxy_peer_address: peer_address, | ||
| proxy_local_address: local_address, | ||
| }; |
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 code is using DownstreamConnectionMetadata::FromProxyProtocol to store metadata from the TLV filter. However, a new DownstreamConnectionMetadata::FromTlv variant was introduced for this purpose. Using the wrong enum variant is incorrect and can lead to logic errors, especially as FromProxyProtocol is intended for PROXY protocol data and has different fields. You should use DownstreamConnectionMetadata::FromTlv here.
let metadata = DownstreamConnectionMetadata::FromTlv {
original_destination_address: original_dest,
tlv_data: std::collections::HashMap::new(),
proxy_peer_address: peer_address,
proxy_local_address: local_address,
};| fn extract_original_destination(&self) -> Option<SocketAddr> { | ||
| let content_start = TLV_TYPE_LEN + TLV_LENGTH_LEN; | ||
|
|
||
| if self.content_length == 0 || self.buffer.len() < content_start + self.content_length as usize { | ||
| return None; | ||
| } | ||
|
|
||
| if self.content_length == TLV_TYPE_SERVICE_ADDRESS_IPV4_LEN { | ||
| let ip_bytes = [ | ||
| self.buffer[content_start], | ||
| self.buffer[content_start + 1], | ||
| self.buffer[content_start + 2], | ||
| self.buffer[content_start + 3], | ||
| ]; | ||
| let port_bytes = [ | ||
| self.buffer[content_start + 4], | ||
| self.buffer[content_start + 5], | ||
| ]; | ||
|
|
||
| let ip = IpAddr::V4(Ipv4Addr::from(ip_bytes)); | ||
| let port = u16::from_be_bytes(port_bytes); | ||
|
|
||
| Some(SocketAddr::new(ip, port)) | ||
|
|
||
| } else if self.content_length == TLV_TYPE_SERVICE_ADDRESS_IPV6_LEN { | ||
| let mut ip_bytes = [0u8; 16]; | ||
| ip_bytes.copy_from_slice(&self.buffer[content_start..content_start + 16]); | ||
| let port_bytes = [ | ||
| self.buffer[content_start + 16], | ||
| self.buffer[content_start + 17], | ||
| ]; | ||
|
|
||
| let ip = IpAddr::V6(Ipv6Addr::from(ip_bytes)); | ||
| let port = u16::from_be_bytes(port_bytes); | ||
|
|
||
| Some(SocketAddr::new(ip, port)) | ||
| } else { | ||
| None | ||
| } | ||
| } |
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.
extract_original_destination incorrectly assumes the service address TLV is always the first TLV in the buffer. It uses a fixed content_start which is only correct for the very first TLV. If other TLVs precede the service address, or if there are multiple service address TLVs, this function will read from the wrong offset or use an incorrect content length, leading to incorrect parsing or panics. The function should iterate through the buffer, parsing each TLV until it finds the first TLV_TYPE_SERVICE_ADDRESS.
fn extract_original_destination(&self) -> Option<SocketAddr> {
let mut idx = 0;
while idx + TLV_TYPE_LEN + TLV_LENGTH_LEN <= self.buffer.len() {
let tlv_type = self.buffer[idx];
if tlv_type == TLV_TYPE_ENDING {
break;
}
let content_len_bytes: [u8; 4] = self.buffer[idx + TLV_TYPE_LEN..idx + TLV_TYPE_LEN + TLV_LENGTH_LEN].try_into().ok()?;
let content_len = u32::from_be_bytes(content_len_bytes);
let content_start = idx + TLV_TYPE_LEN + TLV_LENGTH_LEN;
if content_start + content_len as usize > self.buffer.len() {
// Not enough data for content
return None;
}
if tlv_type == TLV_TYPE_SERVICE_ADDRESS {
if content_len == TLV_TYPE_SERVICE_ADDRESS_IPV4_LEN {
let ip_bytes: [u8; 4] = self.buffer[content_start..content_start + 4].try_into().ok()?;
let port_bytes: [u8; 2] = self.buffer[content_start + 4..content_start + 6].try_into().ok()?;
let ip = IpAddr::V4(Ipv4Addr::from(ip_bytes));
let port = u16::from_be_bytes(port_bytes);
return Some(SocketAddr::new(ip, port));
} else if content_len == TLV_TYPE_SERVICE_ADDRESS_IPV6_LEN {
let mut ip_bytes = [0u8; 16];
ip_bytes.copy_from_slice(&self.buffer[content_start..content_start + 16]);
let port_bytes: [u8; 2] = self.buffer[content_start + 16..content_start + 18].try_into().ok()?;
let ip = IpAddr::V6(Ipv6Addr::from(ip_bytes));
let port = u16::from_be_bytes(port_bytes);
return Some(SocketAddr::new(ip, port));
}
}
idx += TLV_TYPE_LEN + TLV_LENGTH_LEN + content_len as usize;
}
None
}| match metadata { | ||
| DownstreamConnectionMetadata::FromProxyProtocol { original_destination_address, .. } => { | ||
| assert_eq!(original_destination_address, "192.168.1.100:8080".parse::<SocketAddr>().unwrap()); | ||
| }, | ||
| _ => panic!("Expected FromProxyProtocol metadata"), |
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.
This test asserts that the metadata is of type DownstreamConnectionMetadata::FromProxyProtocol. This is incorrect as the TLV filter should produce DownstreamConnectionMetadata::FromTlv. The test should be updated to assert the correct enum variant to match the fix in process_stream.
match metadata {
DownstreamConnectionMetadata::FromTlv { original_destination_address, .. } => {
assert_eq!(original_destination_address, "192.168.1.100:8080".parse::<SocketAddr>().unwrap());
},
_ => panic!("Expected FromTlv metadata"),
}| loop { | ||
| while self.buffer.len() < self.expected_length { | ||
| let bytes_needed = self.expected_length - self.buffer.len(); | ||
| let mut temp_buf = vec![0u8; bytes_needed]; |
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.
f603e89 to
b042a57
Compare
67584f1 to
e6f1e66
Compare
|
@YaoZengzeng kindly review |
| @@ -0,0 +1,372 @@ | |||
| // SPDX-FileCopyrightText: © 2025 kmesh authors | |||
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.
As we are a little far from integrating orion with istio and kmesh.
In order to make sure this filter work properly, it would be good if you could provide a config yaml to run orion with tlv filter and a simple client to access orion.
Through the log of orion, we could verify that all work fine.
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.
As we are a little far from integrating orion with istio and kmesh.
In order to make sure this filter work properly, it would be good if you could provide a config yaml to run orion with tlv filter and a simple client to access orion.
Through the log of orion, we could verify that all work fine.
okkk
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.
As we are a little far from integrating orion with istio and kmesh.
In order to make sure this filter work properly, it would be good if you could provide a config yaml to run orion with tlv filter and a simple client to access orion.
Through the log of orion, we could verify that all work fine.
kindly review the examples folder
|
Would also like @dawid-nowak @awgn @fciaccia to have a look from the framework view |
orion-configuration/build.rs
Outdated
| @@ -0,0 +1,27 @@ | |||
| fn main() -> std::io::Result<()> { | |||
| let protos = vec!["proto/extensions/filters/listener/tlv_listener_filter/v3/tlv_listener_filter.proto"]; | |||
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.
we donot need to have two build script, can add it into the existing one
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.
yaa updated that
...iguration/proto/extensions/filters/listener/tlv_listener_filter/v3/tlv_listener_filter.proto
Outdated
Show resolved
Hide resolved
...iguration/proto/extensions/filters/listener/tlv_listener_filter/v3/tlv_listener_filter.proto
Outdated
Show resolved
Hide resolved
858442e to
216fa4f
Compare
| @@ -0,0 +1,48 @@ | |||
| #!/bin/bash | |||
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.
we can construct a tlv proto packet, and send it to orion to test the result
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.
updated that
- Add simplified TLV listener filter protobuf definition matching kmesh approach - Implement TlvListenerFilter struct with async stream processing for IPv4/IPv6 - Support TLV_TYPE_SERVICE_ADDRESS (0x1) and TLV_TYPE_ENDING (0xfe) - Use empty KmeshTlv message wrapped in UDPA TypedStruct (no configuration parameters) Signed-off-by: Eeshu-Yadav <eeshuyadav123@gmail.com>
216fa4f to
681449d
Compare
|
@Eeshu-Yadav Please donot squash commits before it is ok to merge, keep the commits can simplify reviewing procedure. |
|
LGTM |
hzxuzhonghu
left a comment
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 parsing logic canbe more readable
.gitignore
Outdated
| *.log.* | ||
| *.log | ||
| examples/*/send_tlv | ||
| examples/*/*.log |
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.
we donot want to add such stuff
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.
will remove them
ok |
- Remove unnecessary .gitignore entries - Refactor TLV parsing into smaller methods - Add helper methods for common operations Signed-off-by: Eeshu-Yadav <eeshuyadav123@gmail.com>
|
@hzxuzhonghu all the changes done , as suggested |
| @@ -0,0 +1,21 @@ | |||
| syntax = "proto3"; | |||
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.
please add copyright
hzxuzhonghu
left a comment
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.
@Eeshu-Yadav can you add a CI job to check copyright following up this pr
1a79695 to
89ef906
Compare
|
I mean the copyright checker can be in a separate pr. Our concention is to keep one pr focused and make it as little as possible |
ok will remove that |
611923d to
5a72903
Compare
- Add copyright to kmesh_tlv.proto and send_tlv.rs Signed-off-by: Eeshu-Yadav <eeshuyadav123@gmail.com>
|
@hzxuzhonghu added the copyright to the |
|
/lgtm |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: hzxuzhonghu The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
fixes : #60