-
Notifications
You must be signed in to change notification settings - Fork 127
Make sure we handle IPV6 ips properly when extracting host and port. #5104
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: main
Are you sure you want to change the base?
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.
Thanks for this, @Sylvain-Royer!
Just a couple comments from me. Let me know if you have the capacity to make these changes, otherwise I'm happy to take this over the finish line as well.
(Also seems to be some linting issues that need to be resolved.)
| /// Splits a string address into host and port. If the passed address cannot be parsed, None is returned. | ||
| /// [addr] should be in the following format: "<host>:<port>". | ||
| /// [addr] should be in one of the following formats: | ||
| /// - IPv4/hostname: "<host>:<port>" (e.g., "127.0.0.1:6379") | ||
| /// - IPv6 bracketed: "[<ipv6>]:<port>" (e.g., "[2001:db8::1]:6379") | ||
| /// - IPv6 unbracketed with port: "<ipv6>:<port>" - last segment treated as port (e.g., "2001:db8::1:6379" → host "2001:db8::1", port 6379) | ||
| pub(crate) fn get_host_and_port_from_addr(addr: &str) -> Option<(&str, u16)> { |
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.
Do you have any reason to believe that we need to support unbracketed IPv6 format? My understanding is that RFC 3986 requires brackets, and that this should be the format returned by Valkey/Redis servers.
That would also allow the algorithm to be simplified significantly:
- Split off the last
: - Remove
[]from left string if present
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.
So, if an end user passes in unbracketed values into the initial hosts. this would allow it to work. Now, if we don't want it to work and we want it to fail, then fair enough, I could simplify this logic and only allow for bracketed ipv6.
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.
that said, we can still simplify the algo to be what you described. it would work for both bracketed and unbracketed. Let me do that.
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.
So, if an end user passes in unbracketed values into the initial hosts. this would allow it to work.
If I understand correctly, you are describing the following:
- When the user creates the Valkey cluster servers, they specify IPv6 addresses without brackets.
- When trying to refresh topology,
get_host_and_port_from_addrgets called (internally) with an address string that does not include the brackets.
Am I understanding this correctly?
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.
Yes, I believe so. Though I could be wrong, it is possible that the ipv6 host gets added brackets later on.
glide-core/redis-rs/redis/src/cluster_async/connections_logic.rs
Outdated
Show resolved
Hide resolved
| /// - IPv6 bracketed: "[<ipv6>]:<port>" (e.g., "[2001:db8::1]:6379") | ||
| /// - IPv6 unbracketed with port: "<ipv6>:<port>" - last segment treated as port (e.g., "2001:db8::1:6379" → host "2001:db8::1", port 6379) | ||
| pub fn get_host_and_port_from_addr(addr: &str) -> Option<(&str, u16)> { | ||
| let (host, port_str) = addr.rsplit_once(':')?; |
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 seems reasonable, but might be better to use https://doc.rust-lang.org/std/net/struct.Ipv6Addr.html ?
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.
Sorry, I'm not sure I fully understand how we would use this structure to help out here. This seems to be helpful for IP address which contain both IPv4 and IPv6 in them. Is your thought to use the to_ipv6_compatible function to convert IPv4 to IPv6 compatible IPv4 addresses?
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.
It'd be to use the parse() and ToString() functions instead of manually parsing the format.
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.
Unfortunately, I think we would still need to manually parse out the brackets and remove the port from the original. When I have either [2001:db8:85a3:8d3:1319:8a2e:370:7348]:8080 or [2001:db8:85a3:8d3:1319:8a2e:370:7348] as the host, this host.parse::<Ipv6Addr>() does not return OK. It is only when I have the host be 2001:db8:85a3:8d3:1319:8a2e:370:7348 that it works correctly.
I do think that having that parse will still be useful in terms of validating that it is a valid IPv6 address after doing all the parsing, but I don't believe this can help with parsing. I'll go ahead and add that validation step.
| let host = host | ||
| .strip_prefix('[') | ||
| .and_then(|h| h.strip_suffix(']')) | ||
| .map(|inner| inner.parse::<Ipv6Addr>().ok().map(|_| inner)) | ||
| .unwrap_or(Some(host))?; |
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.
Nit. I think we could make this a bit simpler/more readable (though I am certainly no Rust expert!) 🤷
Using filter:
let host = host
.strip_prefix('[')
.and_then(|h| h.strip_suffix(']'))
.filter(|inner| inner.parse::<Ipv6Addr>().is_ok())
.unwrap_or(host)Or with an if block:
let host = if host.starts_with('[') && host.ends_with(']') {
let inner = &host[1..host.len() - 1];
inner.parse::<Ipv6Addr>().ok()?;
inner
} else {
host
};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.
I think filter makes the most sense. I'm also no rust expert, first time using this language. Updated.
Nevermind, if statement I think is a little better and is more correct/allows for the ipv6 validation to filter out bad ones better.
| #[tokio::test] | ||
| async fn test_get_host_and_port_from_addr_ipv4() { | ||
| assert_eq!( | ||
| get_host_and_port_from_addr("127.0.0.1:6379"), | ||
| Some(("127.0.0.1", 6379)) | ||
| ); | ||
| assert_eq!( | ||
| get_host_and_port_from_addr("192.168.1.1:8080"), | ||
| Some(("192.168.1.1", 8080)) | ||
| ); | ||
| } |
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.
I don't think it is necessary for these to be async:
| #[tokio::test] | |
| async fn test_get_host_and_port_from_addr_ipv4() { | |
| assert_eq!( | |
| get_host_and_port_from_addr("127.0.0.1:6379"), | |
| Some(("127.0.0.1", 6379)) | |
| ); | |
| assert_eq!( | |
| get_host_and_port_from_addr("192.168.1.1:8080"), | |
| Some(("192.168.1.1", 8080)) | |
| ); | |
| } | |
| #[test] | |
| fn test_get_host_and_port_from_addr_ipv4() { | |
| assert_eq!( | |
| get_host_and_port_from_addr("127.0.0.1:6379"), | |
| Some(("127.0.0.1", 6379)) | |
| ); | |
| assert_eq!( | |
| get_host_and_port_from_addr("192.168.1.1:8080"), | |
| Some(("192.168.1.1", 8080)) | |
| ); | |
| } |
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.
Makes sense, updated
Issue link
This Pull Request is linked to issue 5103
Checklist
Before submitting the PR make sure the following are checked: