Browse Source

Add support for IPv6 gateways.

Closes: #207
Approved by: dlrobertson
Valentin Lorentz 7 years ago
parent
commit
4bf917ced3
6 changed files with 158 additions and 41 deletions
  1. 1 1
      Cargo.toml
  2. 13 1
      README.md
  3. 6 2
      examples/httpclient.rs
  4. 74 27
      examples/ping.rs
  5. 58 4
      src/iface/ethernet.rs
  6. 6 6
      src/socket/icmp.rs

+ 1 - 1
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"

+ 13 - 1
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.
 

+ 6 - 2
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![]);

+ 74 - 27
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!()
                 }
             }
 

+ 58 - 4
src/iface/ethernet.rs

@@ -65,6 +65,8 @@ struct InterfaceInner<'b, 'c> {
     ip_addrs:               ManagedSlice<'c, IpCidr>,
     #[cfg(feature = "proto-ipv4")]
     ipv4_gateway:           Option<Ipv4Address>,
+    #[cfg(feature = "proto-ipv6")]
+    ipv6_gateway:           Option<Ipv6Address>,
     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<Ipv4Address>,
+    #[cfg(feature = "proto-ipv6")]
+    ipv6_gateway:        Option<Ipv6Address>,
 }
 
 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<Ipv4Address>
     {
         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<T>(mut self, gateway: T) -> InterfaceBuilder<'b, 'c, DeviceT>
+        where T: Into<Ipv6Address>
+    {
+        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<GatewayAddrT>(&mut self, gateway: GatewayAddrT)
             where GatewayAddrT: Into<Option<Ipv4Address>> {
         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<Ipv6Address> {
+        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<GatewayAddrT>(&mut self, gateway: GatewayAddrT)
+            where GatewayAddrT: Into<Option<Ipv6Address>> {
+        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)
         }
     }

+ 6 - 6
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
         };