Add Address Resolution for IPv6

Add Address Resolution via NDISC for IPv6.

Closes: #196
Approved by: whitequark
v0.7.x
Dan Robertson 2018-04-21 00:36:09 +00:00 committed by Homu
parent 010e55beed
commit bed3d8bd4b
4 changed files with 197 additions and 29 deletions

View File

@ -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"

View File

@ -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)

View File

@ -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]

View File

@ -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")]