Add Address Resolution for IPv6
Add Address Resolution via NDISC for IPv6. Closes: #196 Approved by: whitequarkv0.7.x
parent
010e55beed
commit
bed3d8bd4b
|
@ -58,7 +58,7 @@ required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp"]
|
|||
|
||||
[[example]]
|
||||
name = "ping"
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-icmp"]
|
||||
required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-ipv6", "socket-icmp"]
|
||||
|
||||
[[example]]
|
||||
name = "server"
|
||||
|
|
|
@ -53,7 +53,11 @@ fn main() {
|
|||
let tcp4_socket = TcpSocket::new(tcp4_rx_buffer, tcp4_tx_buffer);
|
||||
|
||||
let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01]);
|
||||
let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24)];
|
||||
let ip_addrs = [
|
||||
IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24),
|
||||
IpCidr::new(IpAddress::v6(0xfdbe, 0x0000, 0x0000, 0x0000,
|
||||
0x0000, 0x0000, 0x0000, 0x002a), 64)
|
||||
];
|
||||
let mut iface = EthernetInterfaceBuilder::new(device)
|
||||
.ethernet_addr(ethernet_addr)
|
||||
.neighbor_cache(neighbor_cache)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -169,6 +169,19 @@ impl Address {
|
|||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
/// The solicited node for the given unicast address.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function panics if the given address is not
|
||||
/// unicast.
|
||||
pub fn solicited_node(&self) -> Address {
|
||||
assert!(self.is_unicast());
|
||||
let mut bytes = [0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
bytes[14..].copy_from_slice(&self.0[14..]);
|
||||
Address(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
|
|
Loading…
Reference in New Issue