Skip to content

Commit

Permalink
fix(ipv6): don't panic if no suitable src_addr
Browse files Browse the repository at this point in the history
Prevent panic when no suitable source address is found. If no suitable
address is found, the loopback address is used instead. The function can
still panic when the destination address is unspecified.

More tests are added:
- Tests when the interface has no addresses. The loopback address is
  used as source address.
- Tests when the interface only has a link-local address. The link-local
  address is used as source address, unless the destination address is
  the loopback address. In this case, the loopback address is used as
  source address.
  • Loading branch information
thvdveld committed Jan 11, 2024
1 parent d185a37 commit d26a313
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 51 deletions.
4 changes: 2 additions & 2 deletions examples/ping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ fn main() {
remote_addr
);
icmp_repr.emit(
&iface.get_source_address_ipv6(&address).unwrap(),
&iface.get_source_address_ipv6(&address),
&address,
&mut icmp_packet,
&device_caps.checksum,
Expand Down Expand Up @@ -221,7 +221,7 @@ fn main() {
let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap();
let icmp_repr = Icmpv6Repr::parse(
&address,
&iface.get_source_address_ipv6(&address).unwrap(),
&iface.get_source_address_ipv6(&address),
&icmp_packet,
&device_caps.checksum,
)
Expand Down
38 changes: 25 additions & 13 deletions src/iface/interface/ipv6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,13 @@ impl Default for HopByHopResponse<'_> {
impl InterfaceInner {
/// Return the IPv6 address that is a candidate source address for the given destination
/// address, based on RFC 6724.
///
/// # Panics
/// This function panics if the destination address is unspecified.
#[allow(unused)]
pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Option<Ipv6Address> {
pub(crate) fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address {
assert!(!dst_addr.is_unspecified());

// See RFC 6724 Section 4: Candidate source address
fn is_candidate_source_address(dst_addr: &Ipv6Address, src_addr: &Ipv6Address) -> bool {
// For all multicast and link-local destination addresses, the candidate address MUST
Expand All @@ -39,10 +44,10 @@ impl InterfaceInner {
return false;
}

// Loopback addresses and multicast address can not be in the candidate source address
// Unspecified addresses and multicast address can not be in the candidate source address
// list. Except when the destination multicast address has a link-local scope, then the
// source address can also be link-local multicast.
if src_addr.is_loopback() || src_addr.is_multicast() {
if src_addr.is_unspecified() || src_addr.is_multicast() {
return false;
}

Expand All @@ -67,18 +72,28 @@ impl InterfaceInner {
bits as usize
}

// Get the first address that is a candidate address.
// If the destination address is a loopback address, or when there are no IPv6 addresses in
// the interface, then the loopback address is the only candidate source address.
if dst_addr.is_loopback()
|| self
.ip_addrs
.iter()
.filter(|a| matches!(a, IpCidr::Ipv6(_)))
.count()
== 0
{
return Ipv6Address::LOOPBACK;
}

let mut candidate = self
.ip_addrs
.iter()
.filter_map(|a| match a {
.find_map(|a| match a {
#[cfg(feature = "proto-ipv4")]
IpCidr::Ipv4(_) => None,
#[cfg(feature = "proto-ipv6")]
IpCidr::Ipv6(a) => Some(a),
})
.find(|a| is_candidate_source_address(dst_addr, &a.address()))
.unwrap();
.unwrap(); // NOTE: we check above that there is at least one IPv6 address.

for addr in self.ip_addrs.iter().filter_map(|a| match a {
#[cfg(feature = "proto-ipv4")]
Expand Down Expand Up @@ -116,7 +131,7 @@ impl InterfaceInner {
}
}

Some(candidate.address())
candidate.address()
}

/// Determine if the given `Ipv6Address` is the solicited node
Expand Down Expand Up @@ -436,11 +451,8 @@ impl InterfaceInner {

let src_addr = if src_addr.is_unicast() {
src_addr
} else if let Some(addr) = self.get_source_address_ipv6(&dst_addr) {
addr
} else {
net_debug!("no suitable source address found");
return None;
self.get_source_address_ipv6(&dst_addr)
};

let ipv6_reply_repr = Ipv6Repr {
Expand Down
4 changes: 2 additions & 2 deletions src/iface/interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ impl Interface {
/// Get an address from the interface that could be used as source address. The selection is
/// based on RFC6724.
#[cfg(feature = "proto-ipv6")]
pub fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Option<Ipv6Address> {
pub fn get_source_address_ipv6(&self, dst_addr: &Ipv6Address) -> Ipv6Address {
self.inner.get_source_address_ipv6(dst_addr)
}

Expand Down Expand Up @@ -728,7 +728,7 @@ impl InterfaceInner {
#[cfg(feature = "proto-ipv4")]
IpAddress::Ipv4(addr) => self.get_source_address_ipv4(addr).map(|a| a.into()),
#[cfg(feature = "proto-ipv6")]
IpAddress::Ipv6(addr) => self.get_source_address_ipv6(addr).map(|a| a.into()),
IpAddress::Ipv6(addr) => Some(self.get_source_address_ipv6(addr).into()),
}
}

Expand Down
Loading

0 comments on commit d26a313

Please sign in to comment.