Skip to content

Change Ipv6Addr::is_loopback to include IPv4-mapped loopback addresses #85655

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

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 122 additions & 27 deletions library/std/src/net/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,62 @@ pub struct Ipv4Addr {
/// IPv6 addresses are defined as 128-bit integers in [IETF RFC 4291].
/// They are usually represented as eight 16-bit segments.
///
/// See [`IpAddr`] for a type encompassing both IPv4 and IPv6 addresses.
///
/// The size of an `Ipv6Addr` struct may vary depending on the target operating
/// system.
///
/// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291
///
/// # Embedding IPv4 Addresses
///
/// See [`IpAddr`] for a type encompassing both IPv4 and IPv6 addresses.
///
/// To assist in the transition from IPv4 to IPv6 two types of IPv6 addresses that embed an IPv4 address were defined:
/// IPv4-compatible and IPv4-mapped addresses. Of these IPv4-compatible addresses have been officially deprecated and are only minimally supported.
///
/// ## IPv4-Compatible IPv6 Addresses
///
/// IPv4-compatible IPv6 addresses are defined in [IETF RFC 4291 Section 2.5.5.1], and have been officially deprecated.
/// The RFC describes the format of an "IPv4-Compatible IPv6 address" as follows:
///
/// ```text
/// | 80 bits | 16 | 32 bits |
/// +--------------------------------------+--------------------------+
/// |0000..............................0000|0000| IPv4 address |
/// +--------------------------------------+----+---------------------+
/// ```
/// So `::a.b.c.d` would be an IPv4-compatible IPv6 address representing the IPv4 address `a.b.c.d`.
///
/// Despite IPv4-compatible IPv6 addresses having been officially deprecated, there is minimal support for handling these addresses:
/// Only converting to and from these addresses is supported, see [`Ipv4Addr::to_ipv6_compatible`] and [`Ipv6Addr::to_ipv4`].
/// No further special meaning is ascribed to these addresses; for example [`is_loopback`](Ipv6Addr::is_loopback) will return `false` for `::127.0.0.1`,
/// even though it represents the IPv4 address `127.0.0.1` (which is a loopback address).
///
/// [IETF RFC 4291 Section 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1
///
/// ## IPv4-Mapped IPv6 Addresses
///
/// IPv4-mapped IPv6 addresses are defined in [IETF RFC 4291 Section 2.5.5.2].
/// The RFC describes the format of an "IPv4-Mapped IPv6 address" as follows:
///
/// ```text
/// | 80 bits | 16 | 32 bits |
/// +--------------------------------------+--------------------------+
/// |0000..............................0000|FFFF| IPv4 address |
/// +--------------------------------------+----+---------------------+
/// ```
/// So `::ffff:a.b.c.d` would be an IPv4-mapped IPv6 address representing the IPv4 address `a.b.c.d`.
///
/// There is more support for handling these addresses than there is for IPv4-compatible addresses:
/// Converting to and from these addresses is supported, see [`Ipv4Addr::to_ipv6_mapped`] and [`Ipv6Addr::to_ipv4`].
/// There is also rudimentary support for the embedded IPv4 addresses; for example [`is_loopback`](Ipv6Addr::is_loopback) will return `true` for `::ffff:127.0.0.1`,
/// because it represents the IPv4 address `127.0.0.1` (which is a loopback address).
///
/// Note: Currently `is_loopback` is the only method that is aware of IPv4-mapped addresses.
///
/// This support for the embedded IPv4 addresses is in line with other languages and the behaviour of many real-world networking hardware.
///
/// [IETF RFC 4291 Section 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2
///
/// # Textual representation
///
/// `Ipv6Addr` provides a [`FromStr`] implementation. There are many ways to represent
Expand Down Expand Up @@ -758,24 +807,25 @@ impl Ipv4Addr {
}
}

/// Converts this address to an IPv4-compatible [`IPv6` address].
/// Converts this address to an [IPv4-compatible] [`IPv6` address].
///
/// `a.b.c.d` becomes `::a.b.c.d`
///
/// This isn't typically the method you want; these addresses don't typically
/// function on modern systems. Use `to_ipv6_mapped` instead.
/// Note that IPv4-compatible addresses have been officially deprecated.
/// If you don't explicitly need an IPv4-compatible address for legacy reasons, consider using `to_ipv6_mapped` instead.
///
/// [IPv4-compatible]: Ipv6Addr#ipv4-compatible-ipv6-addresses
/// [`IPv6` address]: Ipv6Addr
///
/// # Examples
///
/// ```
/// use std::net::{Ipv4Addr, Ipv6Addr};
///
/// assert_eq!(
/// Ipv4Addr::new(192, 0, 2, 255).to_ipv6_compatible(),
/// Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x2ff)
/// );
/// // ::192.0.2.255
/// let ipv6_compatible = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc000, 0x2ff);
///
/// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).to_ipv6_compatible(), ipv6_compatible);
/// ```
#[rustc_const_stable(feature = "const_ipv4", since = "1.50.0")]
#[stable(feature = "rust1", since = "1.0.0")]
Expand All @@ -787,19 +837,22 @@ impl Ipv4Addr {
}
}

/// Converts this address to an IPv4-mapped [`IPv6` address].
/// Converts this address to an [IPv4-mapped] [`IPv6` address].
///
/// `a.b.c.d` becomes `::ffff:a.b.c.d`
///
/// [IPv4-mapped]: Ipv6Addr#ipv4-mapped-ipv6-addresses
/// [`IPv6` address]: Ipv6Addr
///
/// # Examples
///
/// ```
/// use std::net::{Ipv4Addr, Ipv6Addr};
///
/// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).to_ipv6_mapped(),
/// Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff));
/// // ::ffff:192.0.2.255
/// let ipv6_mapped = Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc000, 0x2ff);
///
/// assert_eq!(Ipv4Addr::new(192, 0, 2, 255).to_ipv6_mapped(), ipv6_mapped);
/// ```
#[rustc_const_stable(feature = "const_ipv4", since = "1.50.0")]
#[stable(feature = "rust1", since = "1.0.0")]
Expand Down Expand Up @@ -1193,25 +1246,47 @@ impl Ipv6Addr {
u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::UNSPECIFIED.octets())
}

/// Returns [`true`] if this is a loopback address (::1).
/// Returns [`true`] if this is either:
/// - the [loopback address] as defined in [IETF RFC 4291 section 2.5.3] (`::1`).
/// - an [IPv4-mapped] address representing an IPv4 loopback address as defined in [IETF RFC 1122] (`::ffff:127.0.0.0/104`).
///
/// This property is defined in [IETF RFC 4291].
/// Note that this returns [`false`] for an [IPv4-compatible] address representing an IPv4 loopback address (`::127.0.0.0/104`).
///
/// [IETF RFC 4291]: https://tools.ietf.org/html/rfc4291
/// [loopback address]: Ipv6Addr::LOCALHOST
/// [IETF RFC 4291 section 2.5.3]: https://tools.ietf.org/html/rfc4291#section-2.5.3
/// [IETF RFC 1122]: https://tools.ietf.org/html/rfc1122
/// [IPv4-mapped]: Ipv6Addr#ipv4-mapped-ipv6-addresses
/// [IPv4-compatible]: Ipv6Addr#ipv4-compatible-ipv6-addresses
///
/// # Examples
///
/// ```
/// use std::net::Ipv6Addr;
///
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).is_loopback(), false);
/// // `true` for the IPv6 loopback address (`::1`)
/// assert_eq!(Ipv6Addr::LOCALHOST.is_loopback(), true);
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1).is_loopback(), true);
///
/// use std::net::Ipv4Addr;
///
/// // `true` for an IPv4-mapped address representing an IPv4 loopback address (`::ffff:127.0.0.1`)
/// assert_eq!(Ipv4Addr::LOCALHOST.to_ipv6_mapped().is_loopback(), true);
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0x7f00, 0x1).is_loopback(), true);
///
/// // `false` for an IPv4-compatible address representing an IPv4 loopback address (`::127.0.0.1`)
/// assert_eq!(Ipv4Addr::LOCALHOST.to_ipv6_compatible().is_loopback(), false);
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0x7f00, 0x1).is_loopback(), false);
/// ```
#[rustc_const_stable(feature = "const_ipv6", since = "1.50.0")]
#[stable(since = "1.7.0", feature = "ip_17")]
#[rustc_allow_const_fn_unstable(const_ipv6)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ipv6Addr::to_ipv4_mapped is unstable and unstable const.

#[inline]
pub const fn is_loopback(&self) -> bool {
u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets())
if let Some(ipv4) = self.to_ipv4_mapped() {
ipv4.is_loopback()
} else {
u128::from_be_bytes(self.octets()) == u128::from_be_bytes(Ipv6Addr::LOCALHOST.octets())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changed in this PR, but historical background for anyone wondering why the comparison is written like this:

  • self == Ipv6Addr::LOCALHOST relies on Eq/PartialEq, which is not possible in a const function.
  • self.octets() == Ipv6Addr::LOCALHOST.octets() leads to worse code generation, a problem with comparing arrays.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CDirkx

self.octets() == Ipv6Addr::LOCALHOST.octets() leads to worse code generation, a problem with comparing arrays.

#85828 might fix that.

But also, does u128::from(self) generate reasonable code?

}
}

/// Returns [`true`] if the address appears to be globally routable.
Expand Down Expand Up @@ -1509,13 +1584,14 @@ impl Ipv6Addr {
(self.segments()[0] & 0xff00) == 0xff00
}

/// Converts this address to an [`IPv4` address] if it's an "IPv4-mapped IPv6 address"
/// Converts this address to an [`IPv4` address] if it's an [IPv4-mapped] address as
/// defined in [IETF RFC 4291 section 2.5.5.2], otherwise returns [`None`].
///
/// `::ffff:a.b.c.d` becomes `a.b.c.d`.
/// All addresses *not* starting with `::ffff` will return `None`.
///
/// [`IPv4` address]: Ipv4Addr
/// [IPv4-mapped IPv6 address]: Ipv6Addr
/// [IETF RFC 4291 section 2.5.5.2]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2
///
/// # Examples
Expand All @@ -1525,10 +1601,18 @@ impl Ipv6Addr {
///
/// use std::net::{Ipv4Addr, Ipv6Addr};
///
/// let ipv4 = Ipv4Addr::new(192, 10, 2, 255);
/// let ipv6_compatible = ipv4.to_ipv6_compatible();
/// let ipv6_mapped = ipv4.to_ipv6_mapped();
///
/// // Only IPv4-mapped addresses are converted.
/// assert_eq!(ipv6_compatible.to_ipv4_mapped(), None);
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc00a, 0x2ff).to_ipv4_mapped(), None);
/// assert_eq!(ipv6_mapped.to_ipv4_mapped(), Some(ipv4));
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4_mapped(), Some(ipv4));
///
/// // Addresses that are neither an IPv4-compatible or IPv4-mapped address are not converted.
/// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).to_ipv4_mapped(), None);
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4_mapped(),
/// Some(Ipv4Addr::new(192, 10, 2, 255)));
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_ipv4_mapped(), None);
/// ```
#[rustc_const_unstable(feature = "const_ipv6", issue = "76205")]
#[unstable(feature = "ip", issue = "27709")]
Expand All @@ -1542,23 +1626,34 @@ impl Ipv6Addr {
}
}

/// Converts this address to an [`IPv4` address]. Returns [`None`] if this address is
/// neither IPv4-compatible or IPv4-mapped.
/// Converts this address to an [`IPv4` address] if it's an [IPv4-compatible] address as defined in [IETF RFC 4291 section 2.5.5.1]
/// or an [IPv4-mapped] address as defined in [IETF RFC 4291 section 2.5.5.2], otherwise returns [`None`].
///
/// `::a.b.c.d` and `::ffff:a.b.c.d` become `a.b.c.d`
///
/// [`IPv4` address]: Ipv4Addr
/// [IPv4-compatible]: Ipv6Addr#ipv4-compatible-ipv6-addresses
/// [IPv4-mapped]: Ipv6Addr#ipv4-mapped-ipv6-addresses
/// [IETF RFC 4291 section 2.5.5.1]: https://tools.ietf.org/html/rfc4291#section-2.5.5.1
/// [IETF RFC 4291 section 2.5.5.2]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2
///
/// # Examples
///
/// ```
/// use std::net::{Ipv4Addr, Ipv6Addr};
///
/// let ipv4 = Ipv4Addr::new(192, 10, 2, 255);
/// let ipv6_compatible = ipv4.to_ipv6_compatible();
/// let ipv6_mapped = ipv4.to_ipv6_mapped();
///
/// // Both IPv4-compatible and IPv4-mapped addresses are converted.
/// assert_eq!(ipv6_compatible.to_ipv4(), Some(ipv4));
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0xc00a, 0x2ff).to_ipv4(), Some(ipv4));
/// assert_eq!(ipv6_mapped.to_ipv4(), Some(ipv4));
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4(), Some(ipv4));
///
/// // Addresses that are neither an IPv4-compatible or IPv4-mapped address are not converted.
/// assert_eq!(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0).to_ipv4(), None);
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff).to_ipv4(),
/// Some(Ipv4Addr::new(192, 10, 2, 255)));
/// assert_eq!(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).to_ipv4(),
/// Some(Ipv4Addr::new(0, 0, 0, 1)));
/// ```
#[rustc_const_stable(feature = "const_ipv6", since = "1.50.0")]
#[stable(feature = "rust1", since = "1.0.0")]
Expand Down