|
@@ -20,6 +20,8 @@ use wire::{ArpPacket, ArpRepr, ArpOperation};
|
|
|
use wire::{Icmpv4Packet, Icmpv4Repr, Icmpv4DstUnreachable};
|
|
|
#[cfg(feature = "proto-ipv6")]
|
|
|
use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
|
|
|
+#[cfg(feature = "proto-ipv6")]
|
|
|
+use wire::{NdiscNeighborFlags, NdiscRepr};
|
|
|
#[cfg(all(feature = "proto-ipv6", feature = "socket-udp"))]
|
|
|
use wire::Icmpv6DstUnreachable;
|
|
|
#[cfg(feature = "socket-udp")]
|
|
@@ -267,25 +269,6 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT>
|
|
|
self.inner.ip_addrs.as_ref()
|
|
|
}
|
|
|
|
|
|
- /// Determine if the given `Ipv6Address` is the solicited node
|
|
|
- /// multicast address for a IPv6 addresses assigned to the interface.
|
|
|
- /// See [RFC 4291 § 2.7.1] for more details.
|
|
|
- ///
|
|
|
- /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
|
|
|
- #[cfg(feature = "proto-ipv6")]
|
|
|
- pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
|
|
|
- self.inner.ip_addrs.iter().find(|cidr| {
|
|
|
- match *cidr {
|
|
|
- &IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOOPBACK=> {
|
|
|
- // Take the lower order 24 bits of the IPv6 address and
|
|
|
- // append those bits to FF02:0:0:0:0:1:FF00::/104.
|
|
|
- addr.as_bytes()[14..] == cidr.address().as_bytes()[14..]
|
|
|
- }
|
|
|
- _ => false,
|
|
|
- }
|
|
|
- }).is_some()
|
|
|
- }
|
|
|
-
|
|
|
/// Update the IP addresses of the interface.
|
|
|
///
|
|
|
/// # Panics
|
|
@@ -508,6 +491,25 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Determine if the given `Ipv6Address` is the solicited node
|
|
|
+ /// multicast address for a IPv6 addresses assigned to the interface.
|
|
|
+ /// See [RFC 4291 § 2.7.1] for more details.
|
|
|
+ ///
|
|
|
+ /// [RFC 4291 § 2.7.1]: https://tools.ietf.org/html/rfc4291#section-2.7.1
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ pub fn has_solicited_node(&self, addr: Ipv6Address) -> bool {
|
|
|
+ self.ip_addrs.iter().find(|cidr| {
|
|
|
+ match *cidr {
|
|
|
+ &IpCidr::Ipv6(cidr) if cidr.address() != Ipv6Address::LOOPBACK=> {
|
|
|
+ // Take the lower order 24 bits of the IPv6 address and
|
|
|
+ // append those bits to FF02:0:0:0:0:1:FF00::/104.
|
|
|
+ addr.as_bytes()[14..] == cidr.address().as_bytes()[14..]
|
|
|
+ }
|
|
|
+ _ => false,
|
|
|
+ }
|
|
|
+ }).is_some()
|
|
|
+ }
|
|
|
+
|
|
|
/// Check whether the interface has the given IP address assigned.
|
|
|
fn has_ip_addr<T: Into<IpAddress>>(&self, addr: T) -> bool {
|
|
|
let addr = addr.into();
|
|
@@ -624,7 +626,8 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
|
|
if eth_frame.src_addr().is_unicast() {
|
|
|
// Fill the neighbor cache from IP header of unicast frames.
|
|
|
let ip_addr = IpAddress::Ipv6(ipv6_repr.src_addr);
|
|
|
- if self.in_same_network(&ip_addr) {
|
|
|
+ if self.in_same_network(&ip_addr) &&
|
|
|
+ self.neighbor_cache.lookup_pure(&ip_addr, timestamp).is_none() {
|
|
|
self.neighbor_cache.fill(ip_addr, eth_frame.src_addr(), timestamp);
|
|
|
}
|
|
|
}
|
|
@@ -637,7 +640,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
|
|
|
|
|
match ipv6_repr.next_header {
|
|
|
IpProtocol::Icmpv6 =>
|
|
|
- self.process_icmpv6(sockets, ip_repr, ip_payload),
|
|
|
+ self.process_icmpv6(sockets, timestamp, ip_repr, ip_payload),
|
|
|
|
|
|
#[cfg(feature = "socket-udp")]
|
|
|
IpProtocol::Udp =>
|
|
@@ -735,7 +738,7 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
|
|
}
|
|
|
|
|
|
#[cfg(feature = "proto-ipv6")]
|
|
|
- fn process_icmpv6<'frame>(&mut self, _sockets: &mut SocketSet,
|
|
|
+ fn process_icmpv6<'frame>(&mut self, _sockets: &mut SocketSet, timestamp: Instant,
|
|
|
ip_repr: IpRepr, ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
|
|
|
{
|
|
|
let icmp_packet = Icmpv6Packet::new_checked(ip_payload)?;
|
|
@@ -762,11 +765,67 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
|
|
// Ignore any echo replies.
|
|
|
Icmpv6Repr::EchoReply { .. } => Ok(Packet::None),
|
|
|
|
|
|
+ // Forward any NDISC packets to the ndisc packet handler
|
|
|
+ Icmpv6Repr::Ndisc(repr) if ip_repr.hop_limit() == 0xff => match ip_repr {
|
|
|
+ IpRepr::Ipv6(ipv6_repr) => self.process_ndisc(timestamp, ipv6_repr, repr),
|
|
|
+ _ => Ok(Packet::None)
|
|
|
+ },
|
|
|
+
|
|
|
// FIXME: do something correct here?
|
|
|
_ => Err(Error::Unrecognized),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ fn process_ndisc<'frame>(&mut self, timestamp: Instant, ip_repr: Ipv6Repr,
|
|
|
+ repr: NdiscRepr<'frame>) -> Result<Packet<'frame>> {
|
|
|
+ let packet = match repr {
|
|
|
+ NdiscRepr::NeighborAdvert { lladdr, target_addr, flags } => {
|
|
|
+ let ip_addr = ip_repr.src_addr.into();
|
|
|
+ match lladdr {
|
|
|
+ Some(lladdr) if lladdr.is_unicast() && target_addr.is_unicast() => {
|
|
|
+ if flags.contains(NdiscNeighborFlags::OVERRIDE) {
|
|
|
+ self.neighbor_cache.fill(ip_addr, lladdr, timestamp)
|
|
|
+ } else {
|
|
|
+ if self.neighbor_cache.lookup_pure(&ip_addr, timestamp).is_none() {
|
|
|
+ self.neighbor_cache.fill(ip_addr, lladdr, timestamp)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ _ => (),
|
|
|
+ }
|
|
|
+ Ok(Packet::None)
|
|
|
+ }
|
|
|
+ NdiscRepr::NeighborSolicit { target_addr, lladdr, .. } => {
|
|
|
+ match lladdr {
|
|
|
+ Some(lladdr) if lladdr.is_unicast() && target_addr.is_unicast() => {
|
|
|
+ self.neighbor_cache.fill(ip_repr.src_addr.into(), lladdr, timestamp)
|
|
|
+ },
|
|
|
+ _ => (),
|
|
|
+ }
|
|
|
+ if self.has_solicited_node(ip_repr.dst_addr) && self.has_ip_addr(target_addr) {
|
|
|
+ let advert = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
|
|
|
+ flags: NdiscNeighborFlags::SOLICITED,
|
|
|
+ target_addr: target_addr,
|
|
|
+ lladdr: Some(self.ethernet_addr)
|
|
|
+ });
|
|
|
+ let ip_repr = Ipv6Repr {
|
|
|
+ src_addr: target_addr,
|
|
|
+ dst_addr: ip_repr.src_addr,
|
|
|
+ next_header: IpProtocol::Icmpv6,
|
|
|
+ hop_limit: 0xff,
|
|
|
+ payload_len: advert.buffer_len()
|
|
|
+ };
|
|
|
+ Ok(Packet::Icmpv6((ip_repr, advert)))
|
|
|
+ } else {
|
|
|
+ Ok(Packet::None)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _ => Ok(Packet::None)
|
|
|
+ };
|
|
|
+ packet
|
|
|
+ }
|
|
|
+
|
|
|
#[cfg(feature = "proto-ipv4")]
|
|
|
fn process_icmpv4<'frame>(&self, _sockets: &mut SocketSet, ip_repr: IpRepr,
|
|
|
ip_payload: &'frame [u8]) -> Result<Packet<'frame>>
|
|
@@ -1145,6 +1204,35 @@ impl<'b, 'c> InterfaceInner<'b, 'c> {
|
|
|
|
|
|
Err(Error::Unaddressable)
|
|
|
}
|
|
|
+
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ (&IpAddress::Ipv6(src_addr), IpAddress::Ipv6(dst_addr)) => {
|
|
|
+ net_debug!("address {} not in neighbor cache, sending Neighbor Solicitation",
|
|
|
+ dst_addr);
|
|
|
+
|
|
|
+ let checksum_caps = self.device_capabilities.checksum.clone();
|
|
|
+
|
|
|
+ let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
|
|
|
+ target_addr: src_addr,
|
|
|
+ lladdr: Some(self.ethernet_addr),
|
|
|
+ });
|
|
|
+
|
|
|
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
|
|
|
+ src_addr: src_addr,
|
|
|
+ dst_addr: dst_addr.solicited_node(),
|
|
|
+ next_header: IpProtocol::Icmpv6,
|
|
|
+ payload_len: solicit.buffer_len(),
|
|
|
+ hop_limit: 0xff
|
|
|
+ });
|
|
|
+
|
|
|
+ self.dispatch_ip(tx_token, timestamp, ip_repr, |ip_repr, payload| {
|
|
|
+ solicit.emit(&ip_repr.src_addr(), &ip_repr.dst_addr(),
|
|
|
+ &mut Icmpv6Packet::new(payload), &checksum_caps);
|
|
|
+ })?;
|
|
|
+
|
|
|
+ Err(Error::Unaddressable)
|
|
|
+ }
|
|
|
+
|
|
|
_ => Err(Error::Unaddressable)
|
|
|
}
|
|
|
}
|
|
@@ -1201,7 +1289,9 @@ mod test {
|
|
|
#[cfg(feature = "proto-ipv6")]
|
|
|
use wire::{Ipv6Address, Ipv6Repr};
|
|
|
#[cfg(feature = "proto-ipv6")]
|
|
|
- use wire::{Icmpv6Repr, Icmpv6ParamProblem};
|
|
|
+ use wire::{Icmpv6Packet, Icmpv6Repr, Icmpv6ParamProblem};
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ use wire::{NdiscNeighborFlags, NdiscRepr};
|
|
|
|
|
|
use super::Packet;
|
|
|
|
|
@@ -1213,7 +1303,9 @@ mod test {
|
|
|
#[cfg(feature = "proto-ipv4")]
|
|
|
IpCidr::new(IpAddress::v4(127, 0, 0, 1), 8),
|
|
|
#[cfg(feature = "proto-ipv6")]
|
|
|
- IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128)
|
|
|
+ IpCidr::new(IpAddress::v6(0, 0, 0, 0, 0, 0, 0, 1), 128),
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ IpCidr::new(IpAddress::v6(0xfdbe, 0, 0, 0, 0, 0, 0, 1), 64),
|
|
|
];
|
|
|
|
|
|
let iface = InterfaceBuilder::new(device)
|
|
@@ -1580,6 +1672,65 @@ mod test {
|
|
|
Ok((remote_hw_addr, MockTxToken)));
|
|
|
}
|
|
|
|
|
|
+ #[test]
|
|
|
+ #[cfg(feature = "proto-ipv6")]
|
|
|
+ fn test_handle_valid_ndisc_request() {
|
|
|
+ let (mut iface, mut socket_set) = create_loopback();
|
|
|
+
|
|
|
+ let mut eth_bytes = vec![0u8; 86];
|
|
|
+
|
|
|
+ let local_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 1);
|
|
|
+ let remote_ip_addr = Ipv6Address::new(0xfdbe, 0, 0, 0, 0, 0, 0, 2);
|
|
|
+ let local_hw_addr = EthernetAddress([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
|
|
+ let remote_hw_addr = EthernetAddress([0x52, 0x54, 0x00, 0x00, 0x00, 0x00]);
|
|
|
+
|
|
|
+ let solicit = Icmpv6Repr::Ndisc(NdiscRepr::NeighborSolicit {
|
|
|
+ target_addr: local_ip_addr,
|
|
|
+ lladdr: Some(remote_hw_addr),
|
|
|
+ });
|
|
|
+ let ip_repr = IpRepr::Ipv6(Ipv6Repr {
|
|
|
+ src_addr: remote_ip_addr,
|
|
|
+ dst_addr: local_ip_addr.solicited_node(),
|
|
|
+ next_header: IpProtocol::Icmpv6,
|
|
|
+ hop_limit: 0xff,
|
|
|
+ payload_len: solicit.buffer_len()
|
|
|
+ });
|
|
|
+
|
|
|
+ let mut frame = EthernetFrame::new(&mut eth_bytes);
|
|
|
+ frame.set_dst_addr(EthernetAddress([0x33, 0x33, 0x00, 0x00, 0x00, 0x00]));
|
|
|
+ frame.set_src_addr(remote_hw_addr);
|
|
|
+ frame.set_ethertype(EthernetProtocol::Ipv6);
|
|
|
+ {
|
|
|
+ ip_repr.emit(frame.payload_mut(), &ChecksumCapabilities::default());
|
|
|
+ solicit.emit(&remote_ip_addr.into(), &local_ip_addr.solicited_node().into(),
|
|
|
+ &mut Icmpv6Packet::new(&mut frame.payload_mut()[ip_repr.buffer_len()..]),
|
|
|
+ &ChecksumCapabilities::default());
|
|
|
+ }
|
|
|
+
|
|
|
+ let icmpv6_expected = Icmpv6Repr::Ndisc(NdiscRepr::NeighborAdvert {
|
|
|
+ flags: NdiscNeighborFlags::SOLICITED,
|
|
|
+ target_addr: local_ip_addr,
|
|
|
+ lladdr: Some(local_hw_addr)
|
|
|
+ });
|
|
|
+
|
|
|
+ let ipv6_expected = Ipv6Repr {
|
|
|
+ src_addr: local_ip_addr,
|
|
|
+ dst_addr: remote_ip_addr,
|
|
|
+ next_header: IpProtocol::Icmpv6,
|
|
|
+ hop_limit: 0xff,
|
|
|
+ payload_len: icmpv6_expected.buffer_len()
|
|
|
+ };
|
|
|
+
|
|
|
+ // Ensure an Neighbor Solicitation triggers a Neighbor Advertisement
|
|
|
+ assert_eq!(iface.inner.process_ethernet(&mut socket_set, Instant::from_millis(0), frame.into_inner()),
|
|
|
+ Ok(Packet::Icmpv6((ipv6_expected, icmpv6_expected))));
|
|
|
+
|
|
|
+ // Ensure the address of the requestor was entered in the cache
|
|
|
+ assert_eq!(iface.inner.lookup_hardware_addr(MockTxToken, Instant::from_secs(0),
|
|
|
+ &IpAddress::Ipv6(local_ip_addr), &IpAddress::Ipv6(remote_ip_addr)),
|
|
|
+ Ok((remote_hw_addr, MockTxToken)));
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
#[cfg(feature = "proto-ipv4")]
|
|
|
fn test_handle_other_arp_request() {
|
|
@@ -1694,9 +1845,9 @@ mod test {
|
|
|
new_addrs.extend(addrs.to_vec());
|
|
|
*addrs = From::from(new_addrs);
|
|
|
});
|
|
|
- assert!(iface.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)));
|
|
|
- assert!(iface.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)));
|
|
|
- assert!(!iface.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0001)));
|
|
|
+ assert!(iface.inner.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0002)));
|
|
|
+ assert!(iface.inner.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0xffff)));
|
|
|
+ assert!(!iface.inner.has_solicited_node(Ipv6Address::new(0xff02, 0, 0, 0, 0, 1, 0xff00, 0x0003)));
|
|
|
}
|
|
|
|
|
|
#[test]
|