Skip to content

Commit

Permalink
Merge pull request #895 from thvdveld/fix-ipv6-src-addr-selection
Browse files Browse the repository at this point in the history
fix: don't panic if no suitable IPv6 src_addr is found
  • Loading branch information
thvdveld committed Jan 17, 2024
2 parents cf0c0d4 + d26a313 commit 9bd836b
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 @@ -121,7 +136,7 @@ impl InterfaceInner {
}
}

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

/// Determine if the given `Ipv6Address` is the solicited node
Expand Down Expand Up @@ -441,11 +456,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

0 comments on commit 9bd836b

Please sign in to comment.