Add support for IPv6 gateways.

Closes: #207
Approved by: dlrobertson
This commit is contained in:
Valentin Lorentz 2018-05-13 20:22:18 +02:00 committed by Homu
parent 2afc538fd9
commit 2d716883b6
6 changed files with 157 additions and 40 deletions

View File

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

View File

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

View File

@ -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![]);

View File

@ -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!()
}
}

View File

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

View File

@ -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
};