From c08b8b50ba93a4b3628748b4bc37753308e106cc Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 13 May 2018 20:22:18 +0200 Subject: [PATCH] Add support for IPv6 gateways. --- Cargo.toml | 2 +- README.md | 14 +++++- examples/httpclient.rs | 8 +++- examples/ping.rs | 101 ++++++++++++++++++++++++++++++----------- src/iface/ethernet.rs | 62 +++++++++++++++++++++++-- src/socket/icmp.rs | 12 ++--- 6 files changed, 158 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 253100170..e1e829f9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ required-features = ["std", "phy-raw_socket", "proto-ipv4"] [[example]] name = "httpclient" -required-features = ["std", "phy-tap_interface", "proto-ipv4", "socket-tcp"] +required-features = ["std", "phy-tap_interface", "proto-ipv4", "proto-ipv6", "socket-tcp"] [[example]] name = "ping" diff --git a/README.md b/README.md index 037545caf..bae63b7cf 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,10 @@ a specific user: sudo ip tuntap add name tap0 mode tap user $USER sudo ip link set tap0 up sudo ip addr add 192.168.69.100/24 dev tap0 +sudo ip -6 addr add fe80::100/64 dev tap0 +sudo ip -6 addr add fdaa::100/64 dev tap0 +sudo ip -6 route add fe80::/64 dev tap0 +sudo ip -6 route add fdaa::/64 dev tap0 ``` It's possible to let _smoltcp_ access Internet by enabling routing for the tap interface: @@ -197,6 +201,8 @@ It's possible to let _smoltcp_ access Internet by enabling routing for the tap i ```sh sudo iptables -t nat -A POSTROUTING -s 192.168.69.0/24 -j MASQUERADE sudo sysctl net.ipv4.ip_forward=1 +sudo ip6tables -t nat -A POSTROUTING -s fdaa::/64 -j MASQUERADE +sudo sysctl -w net.ipv6.conf.all.forwarding=1 ``` ### Fault injection @@ -245,7 +251,7 @@ sudo ./target/debug/examples/tcpdump eth0 _examples/httpclient.rs_ emulates a network host that can initiate HTTP requests. -The host is assigned the hardware address `02-00-00-00-00-02` and IPv4 address `192.168.69.1`. +The host is assigned the hardware address `02-00-00-00-00-02`, IPv4 address `192.168.69.1`, and IPv6 address `fdaa::1`. Read its [source code](/examples/httpclient.rs), then run it as: @@ -259,6 +265,12 @@ For example: cargo run --example httpclient -- tap0 93.184.216.34 http://example.org/ ``` +or: + +```sh +cargo run --example httpclient -- tap0 2606:2800:220:1:248:1893:25c8:1946 http://example.org/ +``` + It connects to the given address (not a hostname) and URL, and prints any returned response data. The TCP socket buffers are limited to 1024 bytes to make packet traces more interesting. diff --git a/examples/httpclient.rs b/examples/httpclient.rs index 7f31bee37..4828f8e7d 100644 --- a/examples/httpclient.rs +++ b/examples/httpclient.rs @@ -13,7 +13,7 @@ use std::collections::BTreeMap; use std::os::unix::io::AsRawFd; use url::Url; use smoltcp::phy::wait as phy_wait; -use smoltcp::wire::{EthernetAddress, Ipv4Address, IpAddress, IpCidr}; +use smoltcp::wire::{EthernetAddress, Ipv4Address, Ipv6Address, IpAddress, IpCidr}; use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder}; use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; use smoltcp::time::Instant; @@ -42,13 +42,17 @@ fn main() { let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]); - 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(0xfdaa, 0, 0, 0, 0, 0, 0, 1), 64), + IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)]; let default_v4_gw = Ipv4Address::new(192, 168, 69, 100); + let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100); let mut iface = EthernetInterfaceBuilder::new(device) .ethernet_addr(ethernet_addr) .neighbor_cache(neighbor_cache) .ip_addrs(ip_addrs) .ipv4_gateway(default_v4_gw) + .ipv6_gateway(default_v6_gw) .finalize(); let mut sockets = SocketSet::new(vec![]); diff --git a/examples/ping.rs b/examples/ping.rs index 8ce1869b8..91765ba2b 100644 --- a/examples/ping.rs +++ b/examples/ping.rs @@ -15,12 +15,47 @@ use smoltcp::time::{Duration, Instant}; use smoltcp::phy::Device; use smoltcp::phy::wait as phy_wait; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, + Ipv6Address, Icmpv6Repr, Icmpv6Packet, Ipv4Address, Icmpv4Repr, Icmpv4Packet}; use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder}; use smoltcp::socket::{SocketSet, IcmpSocket, IcmpSocketBuffer, IcmpPacketMetadata, IcmpEndpoint}; use std::collections::HashMap; use byteorder::{ByteOrder, NetworkEndian}; +macro_rules! send_icmp_ping { + ( $repr_type:ident, $packet_type:ident, $ident:expr, $seq_no:expr, + $echo_payload:expr, $socket:expr, $remote_addr:expr ) => {{ + let icmp_repr = $repr_type::EchoRequest { + ident: $ident, + seq_no: $seq_no, + data: &$echo_payload, + }; + + let icmp_payload = $socket + .send(icmp_repr.buffer_len(), $remote_addr) + .unwrap(); + + let mut icmp_packet = $packet_type::new(icmp_payload); + (icmp_repr, icmp_packet) + }} +} + +macro_rules! get_icmp_pong { + ( $repr_type:ident, $repr:expr, $payload:expr, $waiting_queue:expr, $remote_addr:expr, + $timestamp:expr, $received:expr ) => {{ + if let $repr_type::EchoReply { seq_no, data, .. } = $repr { + if let Some(_) = $waiting_queue.get(&seq_no) { + let packet_timestamp_ms = NetworkEndian::read_i64(data); + println!("{} bytes from {}: icmp_seq={}, time={}ms", + data.len(), $remote_addr, seq_no, + $timestamp.total_millis() - packet_timestamp_ms); + $waiting_queue.remove(&seq_no); + $received += 1; + } + } + }} +} + fn main() { utils::setup_logging("warn"); @@ -40,7 +75,7 @@ fn main() { let fd = device.as_raw_fd(); let device = utils::parse_middleware_options(&mut matches, device, /*loopback=*/false); let device_caps = device.capabilities(); - let address = Ipv4Address::from_str(&matches.free[0]).expect("invalid address format"); + let address = IpAddress::from_str(&matches.free[0]).expect("invalid address format"); let count = matches.opt_str("count").map(|s| usize::from_str(&s).unwrap()).unwrap_or(4); let interval = matches.opt_str("interval") .map(|s| Duration::from_secs(u64::from_str(&s).unwrap())) @@ -52,19 +87,23 @@ fn main() { let neighbor_cache = NeighborCache::new(BTreeMap::new()); let remote_addr = address; - let local_addr = Ipv4Address::new(192, 168, 69, 1); let icmp_rx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]); let icmp_tx_buffer = IcmpSocketBuffer::new(vec![IcmpPacketMetadata::EMPTY], vec![0; 256]); let icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer); let ethernet_addr = EthernetAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x02]); - let ip_addr = IpCidr::new(IpAddress::from(local_addr), 24); + let src_ipv6 = IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1); + let ip_addrs = [IpCidr::new(IpAddress::v4(192, 168, 69, 1), 24), + IpCidr::new(src_ipv6, 64), + IpCidr::new(IpAddress::v6(0xfe80, 0, 0, 0, 0, 0, 0, 1), 64)]; let default_v4_gw = Ipv4Address::new(192, 168, 69, 100); + let default_v6_gw = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 0x100); let mut iface = EthernetInterfaceBuilder::new(device) .ethernet_addr(ethernet_addr) - .ip_addrs([ip_addr]) + .ip_addrs(ip_addrs) .ipv4_gateway(default_v4_gw) + .ipv6_gateway(default_v6_gw) .neighbor_cache(neighbor_cache) .finalize(); @@ -77,7 +116,6 @@ fn main() { let mut echo_payload = [0xffu8; 40]; let mut waiting_queue = HashMap::new(); let ident = 0x22b; - let endpoint = IpAddress::Ipv4(remote_addr); loop { iface.poll(&mut sockets, Instant::now()).unwrap(); @@ -93,18 +131,23 @@ fn main() { if socket.can_send() && seq_no < count as u16 && send_at <= timestamp { NetworkEndian::write_i64(&mut echo_payload, timestamp.total_millis()); - let icmp_repr = Icmpv4Repr::EchoRequest { - ident: ident, - seq_no, - data: &echo_payload, - }; - - let icmp_payload = socket - .send(icmp_repr.buffer_len(), endpoint) - .unwrap(); - let mut icmp_packet = Icmpv4Packet::new(icmp_payload); - icmp_repr.emit(&mut icmp_packet, &device_caps.checksum); + match remote_addr { + IpAddress::Ipv4(_) => { + let (icmp_repr, mut icmp_packet) = send_icmp_ping!( + Icmpv4Repr, Icmpv4Packet, ident, seq_no, + echo_payload, socket, remote_addr); + icmp_repr.emit(&mut icmp_packet, &device_caps.checksum); + }, + IpAddress::Ipv6(_) => { + let (icmp_repr, mut icmp_packet) = send_icmp_ping!( + Icmpv6Repr, Icmpv6Packet, ident, seq_no, + echo_payload, socket, remote_addr); + icmp_repr.emit(&src_ipv6, &remote_addr, + &mut icmp_packet, &device_caps.checksum); + }, + _ => unimplemented!() + } waiting_queue.insert(seq_no, timestamp); seq_no += 1; @@ -113,18 +156,22 @@ fn main() { if socket.can_recv() { let (payload, _) = socket.recv().unwrap(); - let icmp_packet = Icmpv4Packet::new(&payload); - let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap(); - - if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr { - if let Some(_) = waiting_queue.get(&seq_no) { - let packet_timestamp_ms = NetworkEndian::read_i64(data); - println!("{} bytes from {}: icmp_seq={}, time={}ms", - data.len(), remote_addr, seq_no, - timestamp.total_millis() - packet_timestamp_ms); - waiting_queue.remove(&seq_no); - received += 1; + + match remote_addr { + IpAddress::Ipv4(_) => { + let icmp_packet = Icmpv4Packet::new(&payload); + let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap(); + get_icmp_pong!(Icmpv4Repr, icmp_repr, payload, + waiting_queue, remote_addr, timestamp, received); } + IpAddress::Ipv6(_) => { + let icmp_packet = Icmpv6Packet::new(&payload); + let icmp_repr = Icmpv6Repr::parse(&remote_addr, &src_ipv6, + &icmp_packet, &device_caps.checksum).unwrap(); + get_icmp_pong!(Icmpv6Repr, icmp_repr, payload, + waiting_queue, remote_addr, timestamp, received); + }, + _ => unimplemented!() } } diff --git a/src/iface/ethernet.rs b/src/iface/ethernet.rs index 529140a4b..c5573101d 100644 --- a/src/iface/ethernet.rs +++ b/src/iface/ethernet.rs @@ -65,6 +65,8 @@ struct InterfaceInner<'b, 'c> { ip_addrs: ManagedSlice<'c, IpCidr>, #[cfg(feature = "proto-ipv4")] ipv4_gateway: Option, + #[cfg(feature = "proto-ipv6")] + ipv6_gateway: Option, device_capabilities: DeviceCapabilities, } @@ -77,6 +79,8 @@ pub struct InterfaceBuilder <'b, 'c, DeviceT: for<'d> Device<'d>> { ip_addrs: ManagedSlice<'c, IpCidr>, #[cfg(feature = "proto-ipv4")] ipv4_gateway: Option, + #[cfg(feature = "proto-ipv6")] + ipv6_gateway: Option, } impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT> @@ -113,7 +117,9 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT> neighbor_cache: None, ip_addrs: ManagedSlice::Borrowed(&mut []), #[cfg(feature = "proto-ipv4")] - ipv4_gateway: None + ipv4_gateway: None, + #[cfg(feature = "proto-ipv6")] + ipv6_gateway: None, } } @@ -158,11 +164,28 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT> where T: Into { let addr = gateway.into(); - InterfaceInner::check_gateway_addr(&addr); + InterfaceInner::check_ipv4_gateway_addr(&addr); self.ipv4_gateway = Some(addr); self } + /// Set the IPv6 gateway the interface will use. See also + /// [ipv6_gateway]. + /// + /// # Panics + /// This function panics if the given address is not unicast. + /// + /// [ipv6_gateway]: struct.EthernetInterface.html#method.ipv6_gateway + #[cfg(feature = "proto-ipv6")] + pub fn ipv6_gateway(mut self, gateway: T) -> InterfaceBuilder<'b, 'c, DeviceT> + where T: Into + { + let addr = gateway.into(); + InterfaceInner::check_ipv6_gateway_addr(&addr); + self.ipv6_gateway = Some(addr); + self + } + /// Set the Neighbor Cache the interface will use. pub fn neighbor_cache(mut self, neighbor_cache: NeighborCache<'b>) -> InterfaceBuilder<'b, 'c, DeviceT> { @@ -192,6 +215,8 @@ impl<'b, 'c, DeviceT> InterfaceBuilder<'b, 'c, DeviceT> ip_addrs: self.ip_addrs, #[cfg(feature = "proto-ipv4")] ipv4_gateway: self.ipv4_gateway, + #[cfg(feature = "proto-ipv6")] + ipv6_gateway: self.ipv6_gateway, } } }, @@ -299,7 +324,24 @@ impl<'b, 'c, DeviceT> Interface<'b, 'c, DeviceT> pub fn set_ipv4_gateway(&mut self, gateway: GatewayAddrT) where GatewayAddrT: Into> { self.inner.ipv4_gateway = gateway.into(); - self.inner.ipv4_gateway.map(|addr| InterfaceInner::check_gateway_addr(&addr)); + self.inner.ipv4_gateway.map(|addr| InterfaceInner::check_ipv4_gateway_addr(&addr)); + } + + /// Get the IPv6 gateway of the interface. + #[cfg(feature = "proto-ipv6")] + pub fn ipv6_gateway(&self) -> Option { + self.inner.ipv6_gateway + } + + /// Set the IPv6 gateway of the interface. + /// + /// # Panics + /// This function panics if the given address is not unicast. + #[cfg(feature = "proto-ipv6")] + pub fn set_ipv6_gateway(&mut self, gateway: GatewayAddrT) + where GatewayAddrT: Into> { + self.inner.ipv6_gateway = gateway.into(); + self.inner.ipv6_gateway.map(|addr| InterfaceInner::check_ipv6_gateway_addr(&addr)); } /// Transmit packets queued in the given sockets, and receive packets queued @@ -490,7 +532,14 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { } #[cfg(feature = "proto-ipv4")] - fn check_gateway_addr(addr: &Ipv4Address) { + fn check_ipv4_gateway_addr(addr: &Ipv4Address) { + if !addr.is_unicast() { + panic!("gateway IP address {} is not unicast", addr); + } + } + + #[cfg(feature = "proto-ipv6")] + fn check_ipv6_gateway_addr(addr: &Ipv6Address) { if !addr.is_unicast() { panic!("gateway IP address {} is not unicast", addr); } @@ -1146,6 +1195,11 @@ impl<'b, 'c> InterfaceInner<'b, 'c> { Some(gateway) => Ok(gateway.into()), None => Err(Error::Unaddressable), } + #[cfg(feature = "proto-ipv6")] + &IpAddress::Ipv6(_) => match self.ipv6_gateway { + Some(gateway) => Ok(gateway.into()), + None => Err(Error::Unaddressable), + } _ => Err(Error::Unaddressable) } } diff --git a/src/socket/icmp.rs b/src/socket/icmp.rs index 747caaa4d..dd822ec30 100644 --- a/src/socket/icmp.rs +++ b/src/socket/icmp.rs @@ -341,7 +341,7 @@ impl<'a, 'b> IcmpSocket<'a, 'b> { let ip_repr = IpRepr::Ipv6(Ipv6Repr { src_addr: src_addr, dst_addr: ipv6_addr, - next_header: IpProtocol::Icmp, + next_header: IpProtocol::Icmpv6, payload_len: repr.buffer_len(), hop_limit: hop_limit, }); @@ -609,7 +609,7 @@ mod test_ipv6 { static LOCAL_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr { src_addr: Ipv6Address::UNSPECIFIED, dst_addr: REMOTE_IPV6, - next_header: IpProtocol::Icmp, + next_header: IpProtocol::Icmpv6, payload_len: 24, hop_limit: 0x40 }); @@ -617,7 +617,7 @@ mod test_ipv6 { static REMOTE_IPV6_REPR: IpRepr = IpRepr::Ipv6(Ipv6Repr { src_addr: REMOTE_IPV6, dst_addr: LOCAL_IPV6, - next_header: IpProtocol::Icmp, + next_header: IpProtocol::Icmpv6, payload_len: 24, hop_limit: 0x40 }); @@ -683,7 +683,7 @@ mod test_ipv6 { assert_eq!(ip_repr, IpRepr::Ipv6(Ipv6Repr { src_addr: Ipv6Address::UNSPECIFIED, dst_addr: REMOTE_IPV6, - next_header: IpProtocol::Icmp, + next_header: IpProtocol::Icmpv6, payload_len: ECHOV6_REPR.buffer_len(), hop_limit: 0x2a, })); @@ -757,7 +757,7 @@ mod test_ipv6 { header: Ipv6Repr { src_addr: LOCAL_IPV6, dst_addr: REMOTE_IPV6, - next_header: IpProtocol::Icmp, + next_header: IpProtocol::Icmpv6, payload_len: 12, hop_limit: 0x40 }, @@ -766,7 +766,7 @@ mod test_ipv6 { let ip_repr = IpRepr::Unspecified { src_addr: REMOTE_IPV6.into(), dst_addr: LOCAL_IPV6.into(), - protocol: IpProtocol::Icmp, + protocol: IpProtocol::Icmpv6, payload_len: icmp_repr.buffer_len(), hop_limit: 0x40 };