Description
Summary:
The issue arises when trying to determine if an IpAddr
is coming from localhost in a mixed IPv4/IPV6 environment.
The is_loopback
function should return true
for loopback IPs such as 127.0.0.1 or [::1]. This fails if a socket is bound to [::] which responds to IPv4 as well as IPv6. Here, IPv4 is automatically wrapped in IPv6 and 127.0.0.1 is becoming [::ffff:127.0.0.1] which is not recognized as a loopback address.
Detailed story
If I bind a server to 0.0.0.0 or [::1] and telnet/curl from localhost, I can easily tell whether the connection came from a local user or not by using the is_loopback
call on IpAddr
, Ipv4Addr
or Ipv6Addr
.
Once I bind my server to [::] to work on IPv4 AND IPv6 at the same time and then connect to it via v4 to 127.0.0.1 the is_loopback
call returns false
.
I then have to manually try conversion of the Ipv6Addr
into an Ipv4Addr
(using to_ipv4
) and perform a second check with is_loopback
.
In my opinion, this behavior should either be clearly documented in the standard library or better yet should happen automatically (at least in IpAddr
) since an ipv6 encapsulated ipv4 loopback address is still a perfectly valid loopback address.
The current documentation in Ipv6Addr
states that it is a check for [::1] but a clear statement that IPv4 in IPv6 loopback addresses are not covered might help. I also guess that having the current minimal checks in both variants (v4 and v6) make sense to keep but the general is_loopback
in IpAddr
itself could provide the convenient conversion as it covers v4 and v6 anyways.
Example Code
use std::net::{Ipv4Addr, Ipv6Addr};
fn main() {
let ipv4 = Ipv4Addr::new(127, 0, 0, 1); // regular 127.0.0.1
let ipv6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1); // regular [::1]
let v4_in_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1); // if binding socket to [::] and connecting from ipv4 localhost
println!("{} is loopback? {} ", ipv4, ipv4.is_loopback());
println!("{} is loopback? {} ", ipv6, ipv6.is_loopback());
println!("{} is loopback? {} ", v4_in_v6, v4_in_v6.is_loopback());
println!("{} is loopback? {} ", v4_in_v6, v4_in_v6.to_ipv4().unwrap().is_loopback());
}
Output:
127.0.0.1 is loopback? true
::1 is loopback? true
::ffff:127.0.0.1 is loopback? false
::ffff:127.0.0.1 is loopback? true