Repository navigation
std::net::IpAddr: is_loopback failing on ipv4-in-ipv6 addresses #69772
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
Activity
the8472 commentedon Mar 8, 2020
This isn't specific to
is_loopback. Pretty much all of the helper methods make ipv6-specific checks.CC #27709
KodrAus commentedon Mar 11, 2020
This case seems like a bug to me, and I think both
Ipv6AddrandIpAddrshould correctly identify the ipv4 loopback in ipv6 as the loopback address.KodrAus commentedon Mar 11, 2020
As a datapoint, .NET does identify
::ffff:127.0.0.1as loopback:sminf commentedon Dec 2, 2020
Same as Go. Playground
Golang check IP whether is IPv4 first, and not only
.IsLoopback()do that, other functions onnet.IPwill do the same thing.Auto merge of rust-lang#86335 - CDirkx:ipv4-in-ipv6, r=dtolnay
jstasiak commentedon Dec 10, 2023
I think with #86335 in place this can be closed as working as expected?
Fix is_loopback() for IPv4-mapped loopbacks
Auto merge of rust-lang#86335 - CDirkx:ipv4-in-ipv6, r=dtolnay